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

C# 委托、事件,lamda表达式

2012-04-05 20:32 351 查看

参考文章

1.委托Delegate

C#中的Delegate对应于C中的指针,但是又有所不同C中的指针既可以指向方法,又可以指向变量,并且可以进行类型转换,

C中的指针实际上就是内存地址变量,他是可以直接操作内存的,通过内存地址直接访问变量,直接调用方法。

而C#中的Delegate是强类型的,也就是说在声明委托时就已经指定了该变量只能指向具有特定参数,以及返回值的方法。

使用delegate就可以直接建立任何名称的委托类型,当进行系统编译时,系统就会自动生成此类型。您可以使用delegatevoidMyDelegate()
方式建立一个委托类,并使用ILDASM.exe观察其成员。由ILDASM.exe中可以看到,它继承了System.MulticastDelegate类,

并自动生成BeginInvoke、EndInvoke、Invoke等三个常用方法。





Invoke方法是用于同步调用委托对象的对应方法,而BeginInvoke、EndInvoke是用于以异步方式调用对应方法的。

publicclassMyDelegate:MulticastDelegate { //同步调用委托方法 publicvirtualvoidInvoke(); //异步调用委托方法 publicvirtualIAsyncResultBeginInvoke(AsyncCallbackcallback,objectstate); publicvirtualvoidEndInvoke(IAsyncResultresult); }
MulticastDelegate是System.Delegate的子类,它是一个特殊类,编译器和其他工具可以从此类派生,但是自定义类不能显式地从此类进行派生。它支持多路广播委托,并拥有一个带有链接的委托列表,在调用多路广播委托时,系统将按照调用列表中的委托出现顺序来同步调用这些委托。

MulticastDelegate具有两个常用属性:Method、Target。其中Method用于获取委托所表示的方法Target用于获取当前调用的类实例。

1.1委托的使用

当建立委托对象时,委托的参数类型必须与委托方法相对应。只要向建立委托对象的构造函数中输入方法名称example.Method,委托就会直接绑定此方法。使用myDelegate.Invoke(stringmessage),就能显式调用委托方法。但在实际的操作中,我们无须用到Invoke方法,而只要直接使用myDelegate(stringmessage),就能调用委托方法。

无返回值的委托

classProgram { delegatevoidMyDelegate(stringmessage); publicclassExample { publicvoidMethod(stringmessage) { MessageBox.Show(message); } } staticvoidMain(string[]args) { Exampleexample=newExample(); MyDelegatemyDelegate=newMyDelegate(example.Method); myDelegate("HelloWorld"); Console.ReadKey(); } }

有返回值的委托

classProgram { delegatestringMyDelegate(stringmessage); publicclassExample { publicstringMethod(stringname) { return"Hello"+name; } } staticvoidMain(string[]args) { Exampleexample=newExample(); //绑定委托方法 MyDelegatemyDelegate=newMyDelegate(example.Method); //调用委托,获取返回值 stringmessage=myDelegate("Leslie"); Console.WriteLine(message); Console.ReadKey(); } }

多路广播委托

delegatedoubleMyDelegate(doublemessage); publicclassPrice { publicdoubleOrdinary(doubleprice) { doubleprice1=0.95*price; Console.WriteLine("OrdinaryPrice:"+price1); returnprice1; } publicdoubleFavourable(doubleprice) { doubleprice1=0.85*price; Console.WriteLine("FavourablePrice:"+price1); returnprice1; } staticvoidMain(string[]args) { Priceprice=newPrice(); //绑定Ordinary方法 MyDelegatemyDelegate=newMyDelegate(price.Ordinary); //绑定Favourable方法 myDelegate+=newMyDelegate(price.Favourable); //调用委托 Console.WriteLine("CurrentPrice:"+myDelegate(100)); Console.ReadKey(); } }
输出





1.2委托的协变与逆变

前面已经说过,委托是强类型的方法指针,但是在面对具有继承关系类型的参数、或者返回值时,委托是如何处理的呢。

协变(返回值类型具有继承关系的方法)

publicclassWorker {.......} publicclassManager:Worker {.......} classProgram { publicdelegateWorkerGetWorkerHandler(intid); //在Framework2.0以上,委托GetWorkerHandler可绑定GetWorker与GetManager两个方法 publicstaticWorkerGetWorker(intid) { Workerworker=newWorker(); returnworker; } publicstaticManagerGetManager(intid) { Managermanager=newManager(); returnmanager; } staticvoidMain(string[]args) { GetWorkerHandlerworkerHandler=newGetWorkerHandler(GetWorker); Workerworker=workerHandler(1); GetWorkerHandlermanagerHandler=newGetWorkerHandler(GetManager); Managermanager=managerHandler(2)asManager; Console.ReadKey(); } }
委托GetWorkerHandler可以绑定GetWorker与GetManager两个方法

逆变

委托逆变,是指委托方法的参数同样可以接收“继承”这个传统规则。像下面的例子,以object为参数的委托,可以接受任何object子类的对象作为参数。最后可以在处理方法中使用is对输入数据的类型进行判断,分别处理对不同的类型的对象。

classProgram { publicdelegatevoidHandler(objectobj); publicstaticvoidGetMessage(objectmessage) { if(messageisstring) Console.WriteLine("Hisnameis:"+message.ToString()); if(messageisint) Console.WriteLine("Hisageis:"+message.ToString()); } staticvoidMain(string[]args) { Handlerhandler=newHandler(GetMessage); handler(29); Console.ReadKey(); } }

注意:委托与其绑定方法的参数必须一至,即当Handler所输入的参数为object类型,其绑定方法GetMessage的参数也必须为object。否则,即使绑定方法的参数为object的子类,系统也无法辨认。

大家可能注意到了,这个委托方法GetMessage的实现不是那么优雅,于是泛型委托应运而生。

classProgram{publicdelegatevoidHandler<T>(Tobj);publicstaticvoidGetWorkerWages(Workerworker){Console.WriteLine("Worker'stotalwagesis"+worker.Wages);}publicstaticvoidGetManagerWages(Managermanager){Console.WriteLine("Manager'stotalwagesis"+manager.Wages);}staticvoidMain(string[]args){Handler<Worker>workerHander=newHandler<Worker>(GetWorkerWages);Workerworker=newWorker();worker.Wages=3000;workerHander(worker);Handler<Manager>managerHandler=newHandler<Manager>(GetManagerWages);Managermanager=newManager();manager.Wages=4500;managerHandler(manager);Console.ReadKey();}}

2.event事件的由来

事件是特殊的委托,他为委托提供了封装性,一方面允许从类的外部增加,删除绑定方法,另一方面又不允许从类的外部来触发委托所绑定了方法。

publicdelegatedoublePriceHandler(); publicclassPriceManager { publicPriceHandlerGetPriceHandler; //委托处理,当价格高于100元按8.8折计算,其他按原价计算 publicdoubleGetPrice() { if(GetPriceHandler.GetInvocationList().Count()>0) { if(GetPriceHandler()>100) returnGetPriceHandler()*0.88; else returnGetPriceHandler(); } return-1; } } classProgram { staticvoidMain(string[]args) { PriceManagerpriceManager=newPriceManager(); //调用priceManager的GetPrice方法获取价格 //直接调用委托的Invoke获取价格,两者进行比较 priceManager.GetPriceHandler=newPriceHandler(ComputerPrice); Console.WriteLine(string.Format("GetPrice\nComputer'spriceis{0}!", priceManager.GetPrice())); Console.WriteLine(string.Format("Invoke\nComputer'spriceis{0}!", priceManager.GetPriceHandler.Invoke())); Console.WriteLine(); priceManager.GetPriceHandler=newPriceHandler(BookPrice); Console.WriteLine(string.Format("GetPrice\nBook'spriceis{0}!", priceManager.GetPrice())); Console.WriteLine(string.Format("Invoke\nBook'spriceis{0}!", priceManager.GetPriceHandler.Invoke())); Console.ReadKey(); } //书本价格为98元 publicstaticdoubleBookPrice() { return98.0; } //计算机价格为8800元 publicstaticdoubleComputerPrice() { return8800.0; } }
以上代码实现了对于100元以上商品的的88折处理。一方面为了给GetPriceHandler绑定方法就必须将委托声明为public,但是一旦声明为public

就可以在类外部直接通过Invoke来调用该委托所绑定的方法,而产生我们不需要的结果。





当然我们可以将GetPriceHandler声明为private并且通过public的addHandler,removeHandler来消除委托public的副作用,但是C#提供了更加优雅的方法:

那就是event关键字。

事件(event)可被视作为一种特别的委托,它为委托对象隐式地建立起add_XXX、remove_XXX两个方法,用作注册与注销事件的处理方法。而且事件对应的变量成员将会被视为private变量,外界无法超越事件所在对象直接访问它们,这使事件具备良好的封装性,而且免除了add_XXX、remove_XXX等繁琐的代码。

publicclassEventTest { publicdelegatevoidMyDelegate(); publiceventMyDelegateMyEvent; }

观察事件的编译过程可知,在编译的时候,系统为MyEvent事件自动建立add_MyEvent、remove_MyEvent方法。





事件能通过+=和-=两个方式注册或者注销对其处理的方法,使用+=与-=操作符的时候,系统会自动调用对应的add_XXX、remove_XXX进行处理。
值得留意,在PersonManager类的Execute方法中,如果MyEvent绑定的处理方法不为空,即可使用MyEvent(string)引发事件。但如果在外界的main方法中直接使用personManager.MyEvent(string)来引发事件,系统将引发错误报告。这正是因为事件具备了良好的封装性,使外界不能超越事件所在的对象访问其变量成员。

注意:在事件所处的对象之外,事件只能出现在+=,-=的左方。

publicdelegatevoidMyDelegate(stringname);

publicclassPersonManager
{
publiceventMyDelegateMyEvent;

//执行事件
publicvoidExecute(stringname)
{
if(MyEvent!=null)
MyEvent(name);
}
}

classProgram
{
staticvoidMain(string[]args)
{
PersonManagerpersonManager=newPersonManager();
//绑定事件处理方法
personManager.MyEvent+=newMyDelegate(GetName);
personManager.Execute("Leslie");
Console.ReadKey();
}

publicstaticvoidGetName(stringname)
{
Console.WriteLine("Mynameis"+name);
}
}

在绑定事件处理方法的时候,事件出现在+=、-=操作符的左边,对应的委托对象出现在+=、-=操作符的右边。对应以上例子,事件提供了更简单的绑定方式,只需要在+=、-=操作符的右方写上方法名称,系统就能自动辩认。

publicdelegatevoidMyDelegate(stringname); publicclassPersonManager { publiceventMyDelegateMyEvent; ......... } classProgram { staticvoidMain(string[]args) { PersonManagerpersonManager=newPersonManager(); //绑定事件处理方法 personManager.MyEvent+=GetName; ............. } publicstaticvoidGetName(stringname) {.........} }
如果觉得编写GetName方法过于麻烦,你还可以使用匿名方法绑定事件的处理。

publicdelegatevoidMyDelegate(stringname); publicclassPersonManager { publiceventMyDelegateMyEvent; //执行事件 publicvoidExecute(stringname) { if(MyEvent!=null) MyEvent(name); } staticvoidMain(string[]args) { PersonManagerpersonManager=newPersonManager(); //使用匿名方法绑定事件的处理 personManager.MyEvent+=delegate(stringname){ Console.WriteLine("Mynameis"+name); }; personManager.Execute("Leslie"); Console.ReadKey(); } }

3.lambda表达式

在Framework2.0以前,声明委托的唯一方法是通过方法命名,从Framework2.0起,系统开始支持匿名方法。
通过匿名方法,可以直接把一段代码绑定给事件,因此减少了实例化委托所需的编码系统开销。
而在Framework3.0开始,Lambda表达式开始逐渐取代了匿名方法,作为编写内联代码的首选方式。总体来说,Lambda表达式的作用是为了使用更简单的方式来编写匿名方法,彻底简化委托的使用方式。

使用匿名方法

staticvoidMain(string[]args)
{
Buttonbtn=newButton();
btn.Click+=delegate(objectobj,EventArgse){
MessageBox.Show("HelloWorld!");
};
}

使用lambda表达式

staticvoidMain(string[]args)
{
Buttonbtn=newButton();
btn.Click+=(objectobj,EventArgse)=>{
MessageBox.Show("HelloWorld!");
};
}

3.1常用泛型委托

publicdelegateboolPredicate<T>(Tobj)

它是一个返回bool的泛型委托,能接受一个任意类型的对象作为参数。

classProgram
{
staticvoidMain(string[]args)
{
List<Person>list=GetList();
//绑定查询条件
Predicate<Person>predicate=newPredicate<Person>(Match);
List<Person>result=list.FindAll(predicate);
Console.WriteLine(“Personcountis:”+result.Count);
Console.ReadKey();
}
//模拟源数据
staticList<Person>GetList()
{
varpersonList=newList<Person>();
varperson1=newPerson(1,"Leslie",29);
personList.Add(person1);
........
returnpersonList;
}
//查询条件
staticboolMatch(Personperson)
{
returnperson.Age<=30;
}
}

publicclassPerson
{
publicPerson(intid,stringname,intage)
{
ID=id;
Name=name;
Age=age;
}

publicintID
{get;set;}
publicstringName
{get;set;}
publicintAge
{get;set;}
}

Action<T>的使用方式与Predicate<T>相似,不同之处在于Predicate<T>返回值为bool,Action<T>的返回值为void。
Action支持0~16个参数,可以按需求任意使用。

publicdelegatevoidAction()

publicdelegatevoidAction<T1>(T1obj1)
publicdelegatevoidAction<T1,T2>(T1obj1,T2obj2)
publicdelegatevoidAction<T1,T2,T3>(T1obj1,T2obj2,T3obj3)
............
publicdelegatevoidAction<T1,T2,T3,......,T16>(T1obj1,T2obj2,T3obj3,......,T16obj16)

staticvoidMain(string[]args)
{
Action<string>action=ShowMessage;
action("HelloWorld");
Console.ReadKey();
}

staticvoidShowMessage(stringmessage)
{
MessageBox.Show(message);
}

委托Func与Action相似,同样支持0~16个参数,不同之处在于Func必须具有返回值

publicdelegateTResultFunc<TResult>()

publicdelegateTResultFunc<T1,TResult>(T1obj1)

publicdelegateTResultFunc<T1,T2,TResult>(T1obj1,T2obj2)

publicdelegateTResultFunc<T1,T2,T3,TResult>(T1obj1,T2obj2,T3obj3)

............

publicdelegateTResultFunc<T1,T2,T3,......,T16,TResult>(T1obj1,T2obj2,T3obj3,......,T16obj16)

staticvoidMain(string[]args)
{
Func<double,bool,double>func=Account;
doubleresult=func(1000,true);
Console.WriteLine("Resultis:"+result);
Console.ReadKey();
}

staticdoubleAccount(doublea,boolcondition)
{
if(condition)
returna*1.5;
else
returna*2;
}

3.2lambda表达式

Lambda的表达式的编写格式如下:

x=>x*1.5

当中“=>”是Lambda表达式的操作符,在左边用作定义一个参数列表,右边可以操作这些参数。

例子一,先把intx设置1000,通过Action把表达式定义为x=x+500,最后通过Invoke激发委托。

staticvoidMain(string[]args)
{
intx=1000;
Actionaction=()=>x=x+500;
action.Invoke();

Console.WriteLine("Resultis:"+x);
Console.ReadKey();
}

例子二,通过Action<int>把表达式定义x=x+500,到最后输入参数1000,得到的结果与例子一相同。
注意,此处Lambda表达式定义的操作使用{}括弧包括在一起,里面可以包含一系列的操作。

staticvoidMain(string[]args)
{
Action<int>action=(x)=>
{
x=x+500;
Console.WriteLine("Resultis:"+x);
};
action.Invoke(1000);
Console.ReadKey();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: