c#中的委托、事件、Func、Predicate、Observer设计模式以及其他
参考资料:
1. 简单谈谈事件与委托
2. C#中的委托和事件(上)
3. C#中的委托和事件(下)
4. delegate,event, lambda,Func,Action以及Predicate
5. 事件与委 托有别, delegate 与 Delegate 相异
7. Delegate,Action,Func, 匿名方法,匿名委托,事件
8. Difference between events and delegates and its respective applications
事件的一个综合性的例子:MSDN中BackgroundWorker 类示例
说明:
由于本文内容比较杂,前边的叙述部分只是破碎条目的聚合,没有逻辑连贯性。请仔细阅读最后的observer设计模式范例的注释,一切尽在注释中了。
委托
委托保持方法的引用
只有与某委托具有相同签名的方法才能被该委托引用。
委托示例一:
//定义一个委托,该委托的签名是:返回int,接受两个参数,一个string一个bool
delegate int SomeDelegate(string s, bool b);
//实例化这个委托。
Class MyClass{//定义一个函数,该函数与SomeDelegate委托具有相同的签名。
private static int SomeFunction(string str, bool bln){
//Do something here.
}
public int SomeFunction2(string str, bool bln){
//Do something here.
}
public static void main(){
SomeDelegate sd;sd = new SomeDelegate(SomeFunction);//给委托赋值方法之一
sd = SomeFunction;//匿名委托,直接用方法名赋值,不用先new一个SomeDelegate
sd += SomeFunction;//对委托绑定方法
sd += new MyClass().SomeFunction2;//非静态方法。
}
}
匿名委托的常见例子:
this.button1.Click += new EventHandler(button1_Click);
但有时候我们也可以匿名地写成这样:
this.button1.Click += button1_Click;
委托的一些总结:
使用+=进行绑定之前必须先使用=给委托赋值,否则会出现“使用了未赋值的局部变量”的编译错误。"
可以将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其绑定的方法。
不管之前这个委托绑定了多少函数,只要一进行赋值,原来绑定的和赋值的方法都被冲掉了。
在委托被赋值(值为某个函数名)之后,程序中凡是该函数名出现的地方,都可以用这个委托代替。
委托示例二:
class Program
{
delegate void D1();
delegate void D2(string myName);
static void Main(string[] args)
{
D1 d1 = new D1(HelloWorld1);
d1();D2 d2 = new D2(HelloWorld2);
d2("Jimmy");d2 = new D2(HelloWorld3);
d2("杨俊明");Console.Read();
}static void HelloWorld1()
{
Console.WriteLine("Hello World!");
}static void HelloWorld2(string name)
{
Console.WriteLine("Hello,{0}!", name);
}static void HelloWorld3(string name)
{
Console.WriteLine("你好,{0}!", name);
}
}
事件
用C语言写一个“事件”的模拟程序
Example.c
//定义一个函数指针func
int (*func) (void);//调用该函数相当于触发了事件。
//该事件触发后,会检查函数指针func是否为NULL,如果不为NULL,说明该指针已被赋值(相当于该事件被注册)。
//如果事件已被注册,则执行之。
void fireTheEvent(){
if(func != NULL){
func();
}
}void registerTheEvent(int (*function) (void)){//为fireTheEvent事件注册监听器。
func = function;
}int callBack(){
printf("Hello~this is a callBack\n");
}int main(){
registerTheEvent(add);//注册事件监听,回调函数为 callBack。
fireTheEvent(); //触发事件。由于已注册过监听,所以事件一旦触发就会调用 callBack 函数.
}
事件只能用 "+=" 或"-=", 这有效地防止了在委托已被其他函数绑定时由于误用=而导致的委托调用链被清空。
event前边的访问修饰符只决定谁能够注册(或说监听)它,并不决定谁能够调用它。就调用来说,无论event声明时有没有public关键字,事件本身都会被编译成private,然后编译器会编译出来两个方法,分别是add和remove,当你用"+="对事件进行绑定时,其实编译器是在调用add方法。而add方法和remove方法的访问限制是由声明事件时候的访问限制符决定的。
因此永远只有声明它的那个类可以调用它,其他类与事件的交互只能通过"+="和"-="。这意味着即使它的子类也不能通过直接调用的方式触发事件。为了使其他类能够触发这个事件,我们可以在声明该事件的类中加一触发该事件的公开方法。当然,如果你希望子类能够重写触发的过程,那么你完全可以把这个公开的方法搞成virtual的。
而普通的委托(delegate)相当于一个字段,声明时候的访问限制符完全决定了委托的访问限制属性。
此处比较拗口,请阅读observer模式范例中的注释。
委托与事件的区别
1. 事件可以被包含在接口的声明中,字段不可以.
例子:
delegate void MsgHandler(string s);
interface ITest
{
event MsgHandler msgNotifier; // compiles
MsgHandler msgNotifier2; // error CS0525: Interfaces cannot contain fields
}class TestClass : ITest
{
public event MsgHandler msgNotifier; // When you implement the interface, you need to implement the event toostatic void Main(string[] args) {}
}
2.
Events are marked as such in the metadata. This allows things like the Windows Forms or ASP.NET designers to distinguish events from mere properties of delegate type, and provide appropriate support for them (specifically showing them on the Events tab of the Properties window).
3.
习惯上:
委托在功能上常被用作“函数指针”,常作为某函数的输入。
事件在功能上常被用于:订阅消息。
delegate和Delegate的差别
而Delegate 是 System.MulticastDelegate 的父类。
delegate 仅仅是 C# 的关键字,表示一个继承自 System.MulticastDelegate 的具体委托类
Delegate 和 System.MulticastDelegate 都是抽象类,只有编译器才可以从此类派生。也就是说,除了用 delegate 这种形式,我们不能显式地从Delegate和System.MulticastDelegate这两个类派生。
匿名方法与Labmda表达式
例子:
class Program
{
delegate void D1();
delegate void D2(string myName);
static void Main(string[] args)
{//正常赋值
D1 d1 = new D1(HelloWorld1);
d1();//用匿名方法给委托赋值
D2 d2 = delegate(string name)
{
Console.WriteLine("Hello,{0}!", name);
};
//用Labmda表达式给委托赋值
d2 = (string name) => { Console.WriteLine("你好,{0}!", name); };
d2("杨俊明");
}static void HelloWorld1()
{
Console.WriteLine("Hello World!");
}}
Lambda表达式可以添加多行语句
(Str1, str2)=>{
Console.WriteLine("哈哈~");
Return str.EndsWith(str2);
}
Lambda表达式若没有参数,则可以用一个空括号来代替。
Action和Func和Predicate
Action、Func、Predicate本质上都是委托
Action是无返回值的泛型委托
Action 表示无参,无返回值的委托
Action<int,string> 表示有传入参数int,string无返回值的委托
Func是有返回值的泛型委托
Func<int> 表示无参,返回值为int的委托
Func<object,string,int> 表示传入参数为object, string 返回值为int的委托
Predicate 是返回bool型的谓语泛型委托
Predicate<int> 表示传入参数为int 返回bool的委托
Action示例:
class Program
{
static Action A1;
static Action<string> A2;
static void Main(string[] args)
{
A1 = new Action(HelloWorld1);
A1();
A2 = new Action<string>(HelloWorld2);
A2("Jimmy");//用Lambda表达式给委托赋值。
A2 = (string name) => { Console.WriteLine("你好,{0}!", name); };
A2("杨俊明");//用匿名方法给委托赋值。
A2 = delegate(string name) { Console.WriteLine("我就是委托,{0} 你说对吗?", name); };
A2("菩提树下的杨过");Console.Read();
}static void HelloWorld1()
{
Console.WriteLine("Hello World!");
}static void HelloWorld2(string name)
{
Console.WriteLine("Hello,{0}!", name);
}
}
Func示例:
class Program
{
static Func<string> F;
static Func<DateTime, string> F2;
static void Main(string[] args)
{
F = new Func<string>(HelloWorld1);
Console.WriteLine(F());F2 = new Func<DateTime, string>(HelloWorld2);
Console.WriteLine(F2(DateTime.Now));Console.Read();
}static string HelloWorld1()
{
return "Hello World!";
}
static string HelloWorld2(DateTime time)
{
return string.Format("Hello World,the time is {0}.", time);
}
}
Observer设计模式
Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。
Observer模式主要包括如下两类对象:
Subject:被监视对象,它往往包含着其他对象所感兴趣的内容。在本范例中,热水器就是一个被监视对象,它的temprature字段就是其他对象感兴趣的内容,当这个字段的值大于95时时,会不断把数据发给监视它的对象。
Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。在本范例中,Observer有警报器和显示器,它们采取的行动分别是发出警报和显示水温。
假设热水器由三部分组成:heater(热水器)、alarm(警报器)、display(显示器),它们来自于不同厂商并进行了组装。那么,应该是heater仅仅负责烧水,它不能发出警报也不能显示水温;在水烧开时由alarm发出警报、display显示水温。
怎么实现“水烧开时,heater通知alarm和display”呢。我们通过“事件”来实现。
范例:
using System;
namespace Delegate {
// 热水器
public class Heater {private int temperature;
public delegate void BoilHandler(int param); //声明委托
public event BoilHandler BoilEvent; //声明事件
// 烧水
public void BoilWater() {
for (int i = 0; i <= 100; i++) {
temperature = i;
if (temperature > 95) {//水温大于95度时
if (BoilEvent != null) {//如果有对象注册
BoilEvent(temperature); //调用所有注册对象的方法。
}
}
}
}
}// 警报器
public class Alarm {
public void MakeAlert(int param) {
Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:", param);
}
}
// 显示器
public class Display {
public static void ShowMsg(int param) {//静态方法
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", param);
}
}class Program {
static void Main() {
Heater heater = new Heater();
Alarm alarm = new Alarm();heater.BoilEvent += alarm.MakeAlert;//注册方法
heater.BoilEvent += (new Alarm()).MakeAlert; //给匿名对象注册方法
heater.BoilEvent += Display.ShowMsg; //注册静态方法//烧水,水温大于95度时,会触发事件,自动调用注册过监听该事件的方法。
heater.BoilWater();
}
}
}
但这个范例不符合.net framework规范。符合.net规范的应该是
委托类型的名称都应该以EventHandler结束。委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型(代表了产生该事件的那个对象的引用),一个 EventArgs类型(事件产生时候传递的参数)。
这些不仅仅是为了编码规范,也使得程序有更大的灵活性。比如说,如果我们不光想获得热水器的温度,还想在Observer端 (警报器或者显示器)方法中获得它的生产日期、型号、价格,那么委托和方法的声明都会变得很麻烦,而如果我们将热水器的引用传给警报器的方法,就可以在方法中直接访问热水器了,也就不用在参数e中传递过多的信息了。
符合.net规范的observer模式范例:
using System;
namespace Delegate
{// 热水器类
public class Heater
{private int temperature;
public string type = "RealFire 001";// 添加型号作为演示
public string area = "Made in China"; // 添加产地作为演示//声明委托
//当事件触发时,就把this当sender传递出去,是的当初注册这个事件的对象能够通过sender拿到当前heater的引用。
//而e中包含了当事件被触发时,heater想要向外界传递出去的信息。本例中只有此时的温度。
public delegate void BoiledEventHandler(Object sender, BoliedEventArgs e);
//声明一个'水烧开了'事件,当水温达到95度,这个事件会被触发。
//该事件的类型是BoiledEventHandler。
//这里的public决定的是Boiled的"+="和"-="的访问权限而不是Boiled本身的访问权限。Boiled本身会被编译为private。
//也就是说你可以在Program类中Heater heater = new heater. 然后heater.Boiled += functionName;
//但不能直接通过heater.Boiled来触发Boiled事件。因为Boiled是private的,只有在heater内部才能通过直接调用来触发它。
//本例中它在OnBolied方法中被直接调用,从而触发事件。
public event BoiledEventHandler Boiled;// 定义BoliedEventArgs类,传递给Observer所感兴趣的信息
// 如果你愿意,可以把这个类定义的很复杂,传递巨多信息。
//不过没必要太复杂,因为通过object参数可以直接得到被监听者的引用。
public class BoliedEventArgs : EventArgs
{
public readonly int temperature;
public BoliedEventArgs(int temperature)
{
this.temperature = temperature;
}
}//heater开始烧水,达到95度则调用OnBolied函数,传递一个参数e。
//OnBolied函数会触发“水烧开了”事件。
public void BoilWater()
{
for (int i = 0; i <= 100; i++)
{
temperature = i;
if (temperature > 95)
{//建立BoliedEventArgs 对象。
BoliedEventArgs e = new BoliedEventArgs(temperature);// 调用 OnBolied方法触发事件,并把携带信息的参数e传入。
OnBolied(e);
}
}
}// 该方法触发"水烧开了"事件。
// 它检查当初都有哪些对象表示对这个事件感兴趣,并依次调用那些对象当初留下来的方法。
// 它不管这些方法是谁当初留下来的,只管一一调用他们就是了。
// 该方法为虚方法,可以供继承自 Heater 的类重写。
protected virtual void OnBolied(BoliedEventArgs e)
{
if (Boiled != null)// 如果有对象注册
{
// 触发Boiled事件。这会依次调用所有已注册的方法
Boiled(this, e);
}
}} //热水器结束
// 警报器
public class Alarm
{public void MakeAlert(Object sender, Heater.BoliedEventArgs e)
{
Heater heater = (Heater)sender;// 可以通过sender拿到触发这个事件的那个heater的引用,从而访问heater中的公共字段
Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type);Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", e.temperature);
Console.WriteLine();
}} //警报器结束
// 显示器
public class Display
{public static void ShowMsg(Object sender, Heater.BoliedEventArgs e)
{ //静态方法
Heater heater = (Heater)sender;//访问 sender 中的公共字段
Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type);
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature);
Console.WriteLine();
}} //显示器结束
//主程序
class Program
{
static void Main()
{
Heater heater = new Heater();
Alarm alarm = new Alarm();
Display display = new Display();//为boiled事件注册监听方法。
//注册的过程其实不是直接对heater.Boiled进行+=操作的过程。
//因为Boiled是private的,其他类根本碰不到它。
//这里进行的+=操作其实是通过调用编译器为我们编译出来的一个add方法来添加注册的。
//add方法的访问控制属性由声明事件时候访问控制关键字决定。
//因为heater的Boiled事件是public的,所以我们在这里可以对heater.Boiled进行"+="操作。
//对某个事件进行"+="操作意味着表示对该事件感兴趣,并留下了一个方法,告诉该事件,当你被触发时,就调用我留下的这个方法。
heater.Boiled += alarm.MakeAlert; //注册方法
heater.Boiled += (new Alarm()).MakeAlert; //给匿名对象注册方法
heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以这么注册
heater.Boiled += Display.ShowMsg; //注册静态方法。//开始烧水。
heater.BoilWater();
}
} //主程序结束。
}