您的位置:首页 > 编程语言 > C#

c#中的委托、事件、Func、Predicate、Observer设计模式以及其他

2010-08-21 16:23 477 查看

原文地址:[学习笔记]c#中的委托、事件、Func、Predicate、Observer设计模式以及其他


参考资料:

1.简单谈谈事件与委托

2.C#中的委托和事件(上)

3.C#中的委托和事件(下)

4.delegate,event,lambda,Func,Action以及Predicate

5.事件与委托有别,delegate与Delegate相异

6.C#eventsvs.delegates

7.Delegate,Action,Func,匿名方法,匿名委托,事件

8.Differencebetweeneventsanddelegatesanditsrespectiveapplications


事件的一个综合性的例子:MSDN中BackgroundWorker类示例

说明:


由于本文内容比较杂,前边的叙述部分只是破碎条目的聚合,没有逻辑连贯性。请仔细阅读最后的observer设计模式范例的注释,一切尽在注释中了。



委托


委托保持方法的引用

只有与某委托具有相同签名的方法才能被该委托引用。



委托示例一:

//定义一个委托,该委托的签名是:返回int,接受两个参数,一个string一个bool
delegateintSomeDelegate(strings,boolb);

//实例化这个委托。
ClassMyClass{

[code]  //定义一个函数,该函数与SomeDelegate委托具有相同的签名。
  privatestaticintSomeFunction(stringstr,boolbln){
    //Dosomethinghere.
  }
 
 publicintSomeFunction2(stringstr,boolbln){ 
  //Dosomethinghere.
 }


 publicstaticvoidmain(){



[/code]

SomeDelegatesd;

sd=newSomeDelegate(SomeFunction);//给委托赋值方法之一

sd=SomeFunction;//匿名委托,直接用方法名赋值,不用先new一个SomeDelegate

sd+=SomeFunction;//对委托绑定方法

     sd+=newMyClass().SomeFunction2;//非静态方法。

   }

}



匿名委托的常见例子:

this.button1.Click+=newEventHandler(button1_Click);

但有时候我们也可以匿名地写成这样:

this.button1.Click+=button1_Click;



委托的一些总结:

使用+=进行绑定之前必须先使用=给委托赋值,否则会出现“使用了未赋值的局部变量”的编译错误。"

可以将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其绑定的方法。

不管之前这个委托绑定了多少函数,只要一进行赋值,原来绑定的和赋值的方法都被冲掉了。

在委托被赋值(值为某个函数名)之后,程序中凡是该函数名出现的地方,都可以用这个委托代替。



委托示例二:

classProgram
{
delegatevoidD1();
delegatevoidD2(stringmyName);

staticvoidMain(string[]args)
{
D1d1=newD1(HelloWorld1);
d1();

D2d2=newD2(HelloWorld2);
d2("Jimmy");

d2=newD2(HelloWorld3);
d2("杨俊明");

Console.Read();
}

staticvoidHelloWorld1()
{
Console.WriteLine("HelloWorld!");
}

staticvoidHelloWorld2(stringname)
{
Console.WriteLine("Hello,{0}!",name);
}

staticvoidHelloWorld3(stringname)
{
Console.WriteLine("你好,{0}!",name);
}
}


事件


用C语言写一个“事件”的模拟程序

Example.c

//定义一个函数指针func
int(*func)(void);

//调用该函数相当于触发了事件。
//该事件触发后,会检查函数指针func是否为NULL,如果不为NULL,说明该指针已被赋值(相当于该事件被注册)。
//如果事件已被注册,则执行之。
voidfireTheEvent(){
if(func!=NULL){
func();
}
}

voidregisterTheEvent(int(*function)(void)){//为fireTheEvent事件注册监听器。
func=function;
}

intcallBack(){
printf("Hello~thisisacallBack\n");
}

intmain(){
registerTheEvent(add);//注册事件监听,回调函数为callBack。
fireTheEvent();//触发事件。由于已注册过监听,所以事件一旦触发就会调用callBack函数.
}




事件只能用"+="或"-=",这有效地防止了在委托已被其他函数绑定时由于误用=而导致的委托调用链被清空。




event前边的访问修饰符只决定谁能够注册(或说监听)它,并不决定谁能够调用它。就调用来说,无论event声明时有没有public关键字,事件本身都会被编译成private,然后编译器会编译出来两个方法,分别是add和remove,当你用"+="对事件进行绑定时,其实编译器是在调用add方法。而add方法和remove方法的访问限制是由声明事件时候的访问限制符决定的。

因此永远只有声明它的那个类可以调用它,其他类与事件的交互只能通过"+="和"-="。这意味着即使它的子类也不能通过直接调用的方式触发事件。为了使其他类能够触发这个事件,我们可以在声明该事件的类中加一触发该事件的公开方法。当然,如果你希望子类能够重写触发的过程,那么你完全可以把这个公开的方法搞成virtual的。

而普通的委托(delegate)相当于一个字段,声明时候的访问限制符完全决定了委托的访问限制属性。

此处比较拗口,请阅读observer模式范例中的注释。



委托与事件的区别


1.事件可以被包含在接口的声明中,字段不可以.

例子:

delegatevoidMsgHandler(strings);

interfaceITest
{
eventMsgHandlermsgNotifier;//compiles
MsgHandlermsgNotifier2;//errorCS0525:Interfacescannotcontainfields
}

classTestClass:ITest
{
publiceventMsgHandlermsgNotifier;//Whenyouimplementtheinterface,youneedtoimplementtheeventtoo

staticvoidMain(string[]args){}
}





2.

Eventsaremarkedassuchinthemetadata.ThisallowsthingsliketheWindowsFormsorASP.NETdesignerstodistinguisheventsfrommerepropertiesofdelegatetype,andprovideappropriatesupportforthem(specificallyshowingthemontheEventstabofthePropertieswindow).




3.

习惯上:

委托在功能上常被用作“函数指针”,常作为某函数的输入。

事件在功能上常被用于:订阅消息。



delegate和Delegate的差别



而Delegate是System.MulticastDelegate的父类。

delegate仅仅是C#的关键字,表示一个继承自System.MulticastDelegate的具体委托类

Delegate和System.MulticastDelegate都是抽象类,只有编译器才可以从此类派生。也就是说,除了用delegate这种形式,我们不能显式地从Delegate和System.MulticastDelegate这两个类派生。



匿名方法与Labmda表达式


例子:

classProgram
{
delegatevoidD1();
delegatevoidD2(stringmyName);

staticvoidMain(string[]args)
{

//正常赋值

D1d1=newD1(HelloWorld1);
d1();

//用匿名方法给委托赋值
D2d2=delegate(stringname)
{
Console.WriteLine("Hello,{0}!",name);
};
//用Labmda表达式给委托赋值
d2=(stringname)=>{Console.WriteLine("你好,{0}!",name);};
d2("杨俊明");
}

staticvoidHelloWorld1()
{
Console.WriteLine("HelloWorld!");
}

}




Lambda表达式可以添加多行语句

(Str1,str2)=>{
Console.WriteLine("哈哈~");
Returnstr.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示例:

classProgram
{
staticActionA1;
staticAction<string>A2;

staticvoidMain(string[]args)
{
A1=newAction(HelloWorld1);
A1();

A2=newAction<string>(HelloWorld2);
A2("Jimmy");

//用Lambda表达式给委托赋值。
A2=(stringname)=>{Console.WriteLine("你好,{0}!",name);};
A2("杨俊明");

//用匿名方法给委托赋值。
A2=delegate(stringname){Console.WriteLine("我就是委托,{0}你说对吗?",name);};
A2("菩提树下的杨过");

Console.Read();
}

staticvoidHelloWorld1() { Console.WriteLine("HelloWorld!"); }

staticvoidHelloWorld2(stringname)
{
Console.WriteLine("Hello,{0}!",name);
}

}





Func示例:

classProgram
{
staticFunc<string>F;
staticFunc<DateTime,string>F2;

staticvoidMain(string[]args)
{
F=newFunc<string>(HelloWorld1);
Console.WriteLine(F());

F2=newFunc<DateTime,string>(HelloWorld2);
Console.WriteLine(F2(DateTime.Now));

Console.Read();
}

staticstringHelloWorld1()
{
return"HelloWorld!";
}

staticstringHelloWorld2(DateTimetime)
{
returnstring.Format("HelloWorld,thetimeis{0}.",time);
}

}





Observer设计模式


Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。



Observer模式主要包括如下两类对象:

Subject:被监视对象,它往往包含着其他对象所感兴趣的内容。在本范例中,热水器就是一个被监视对象,它的temprature字段就是其他对象感兴趣的内容,当这个字段的值大于95时时,会不断把数据发给监视它的对象。

Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。在本范例中,Observer有警报器和显示器,它们采取的行动分别是发出警报和显示水温。



假设热水器由三部分组成:heater(热水器)、alarm(警报器)、display(显示器),它们来自于不同厂商并进行了组装。那么,应该是heater仅仅负责烧水,它不能发出警报也不能显示水温;在水烧开时由alarm发出警报、display显示水温。

怎么实现“水烧开时,heater通知alarm和display”呢。我们通过“事件”来实现。


范例:


usingSystem;

namespaceDelegate{

//热水器
publicclassHeater{

privateinttemperature;
publicdelegatevoidBoilHandler(intparam);//声明委托
publiceventBoilHandlerBoilEvent;//声明事件

//烧水
publicvoidBoilWater(){
for(inti=0;i<=100;i++){
temperature=i;
if(temperature>95){//水温大于95度时
if(BoilEvent!=null){//如果有对象注册
BoilEvent(temperature);//调用所有注册对象的方法。
}
}
}
}

}

//警报器
publicclassAlarm{
publicvoidMakeAlert(intparam){
Console.WriteLine("Alarm:嘀嘀嘀,水已经{0}度了:",param);
}
}

//显示器
publicclassDisplay{
publicstaticvoidShowMsg(intparam){//静态方法
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。",param);
}
}

classProgram{

staticvoidMain(){
Heaterheater=newHeater();
Alarmalarm=newAlarm();

heater.BoilEvent+=alarm.MakeAlert;//注册方法
heater.BoilEvent+=(newAlarm()).MakeAlert;//给匿名对象注册方法
heater.BoilEvent+=Display.ShowMsg;//注册静态方法

//烧水,水温大于95度时,会触发事件,自动调用注册过监听该事件的方法。
heater.BoilWater();

}
}
}





但这个范例不符合.netframework规范。符合.net规范的应该是

委托类型的名称都应该以EventHandler结束。委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object类型(代表了产生该事件的那个对象的引用),一个EventArgs类型(事件产生时候传递的参数)。

这些不仅仅是为了编码规范,也使得程序有更大的灵活性。比如说,如果我们不光想获得热水器的温度,还想在Observer端(警报器或者显示器)方法中获得它的生产日期、型号、价格,那么委托和方法的声明都会变得很麻烦,而如果我们将热水器的引用传给警报器的方法,就可以在方法中直接访问热水器了,也就不用在参数e中传递过多的信息了。


符合.net规范的observer模式范例:


usingSystem;

namespaceDelegate
{

//热水器类
publicclassHeater
{

privateinttemperature;
publicstringtype="RealFire001";//添加型号作为演示
publicstringarea="MadeinChina";//添加产地作为演示

//声明委托


//当事件触发时,就把this当sender传递出去,是的当初注册这个事件的对象能够通过sender拿到当前heater的引用。

//而e中包含了当事件被触发时,heater想要向外界传递出去的信息。本例中只有此时的温度。
publicdelegatevoidBoiledEventHandler(Objectsender,BoliedEventArgse);

//声明一个'水烧开了'事件,当水温达到95度,这个事件会被触发。
//该事件的类型是BoiledEventHandler。
//这里的public决定的是Boiled的"+="和"-="的访问权限而不是Boiled本身的访问权限。Boiled本身会被编译为private。
//也就是说你可以在Program类中Heaterheater=newheater.然后heater.Boiled+=functionName;
//但不能直接通过heater.Boiled来触发Boiled事件。因为Boiled是private的,只有在heater内部才能通过直接调用来触发它。
//本例中它在OnBolied方法中被直接调用,从而触发事件。
publiceventBoiledEventHandlerBoiled;

//定义BoliedEventArgs类,传递给Observer所感兴趣的信息
//如果你愿意,可以把这个类定义的很复杂,传递巨多信息。
//不过没必要太复杂,因为通过object参数可以直接得到被监听者的引用。
publicclassBoliedEventArgs:EventArgs
{
publicreadonlyinttemperature;
publicBoliedEventArgs(inttemperature)
{
this.temperature=temperature;
}
}

//heater开始烧水,达到95度则调用OnBolied函数,传递一个参数e。
//OnBolied函数会触发“水烧开了”事件。
publicvoidBoilWater()
{
for(inti=0;i<=100;i++)
{
temperature=i;
if(temperature>95)
{

//建立BoliedEventArgs对象。
BoliedEventArgse=newBoliedEventArgs(temperature);

//调用OnBolied方法触发事件,并把携带信息的参数e传入。
OnBolied(e);
}
}
}

//该方法触发"水烧开了"事件。
//它检查当初都有哪些对象表示对这个事件感兴趣,并依次调用那些对象当初留下来的方法。
//它不管这些方法是谁当初留下来的,只管一一调用他们就是了。
//该方法为虚方法,可以供继承自Heater的类重写。
protectedvirtualvoidOnBolied(BoliedEventArgse)
{
if(Boiled!=null)//如果有对象注册
{
//触发Boiled事件。这会依次调用所有已注册的方法
Boiled(this,e);
}
}

}//热水器结束

//警报器
publicclassAlarm
{

publicvoidMakeAlert(Objectsender,Heater.BoliedEventArgse)
{
Heaterheater=(Heater)sender;

//可以通过sender拿到触发这个事件的那个heater的引用,从而访问heater中的公共字段
Console.WriteLine("Alarm:{0}-{1}:",heater.area,heater.type);

Console.WriteLine("Alarm:嘀嘀嘀,水已经{0}度了:",e.temperature);
Console.WriteLine();
}

}//警报器结束

//显示器
publicclassDisplay
{

publicstaticvoidShowMsg(Objectsender,Heater.BoliedEventArgse)
{//静态方法
Heaterheater=(Heater)sender;

//访问sender中的公共字段
Console.WriteLine("Display:{0}-{1}:",heater.area,heater.type);
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。",e.temperature);
Console.WriteLine();
}

}//显示器结束

//主程序
classProgram
{
staticvoidMain()
{
Heaterheater=newHeater();
Alarmalarm=newAlarm();
Displaydisplay=newDisplay();

//为boiled事件注册监听方法。
//注册的过程其实不是直接对heater.Boiled进行+=操作的过程。
//因为Boiled是private的,其他类根本碰不到它。
//这里进行的+=操作其实是通过调用编译器为我们编译出来的一个add方法来添加注册的。
//add方法的访问控制属性由声明事件时候访问控制关键字决定。
//因为heater的Boiled事件是public的,所以我们在这里可以对heater.Boiled进行"+="操作。
//对某个事件进行"+="操作意味着表示对该事件感兴趣,并留下了一个方法,告诉该事件,当你被触发时,就调用我留下的这个方法。
heater.Boiled+=alarm.MakeAlert;//注册方法
heater.Boiled+=(newAlarm()).MakeAlert;//给匿名对象注册方法
heater.Boiled+=newHeater.BoiledEventHandler(alarm.MakeAlert);//也可以这么注册
heater.Boiled+=Display.ShowMsg;//注册静态方法。

//开始烧水。
heater.BoilWater();

}
}//主程序结束。
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: