委托、事件和Lambda
一、委托 delegate
1.在.Net平台下,委托类型用来定义和响应应用程序中的回调。事实上,.Net委托类型是一个类型安全的对象,指向可以以后调用的其他方法,.Net委托是内置支持
多路广播和异步方法调用的对象。.Net委托是类型安全的,如果将一个不“匹配模式”的方法传入委托,将会收到编译器错误。
2.委托类型包含3个重要的信息:
(1)它所调用的方法的名称;
(2)该方法的参数(可选);
(3)该方法的返回值(可选)。
.Net委托既可指向静态方法,也可以指向实例方法。
3.定义委托类型:public delegate int BinaryOp(int x, int y);
经过反编译:委托类型BinaryOp自动产生一个派生自System.MulticastDelegate(这是一个抽象类)的密封类,并生产3个方法:
(1)public virtual int Invoke(int x, int y);它被用来以同步方式调用委托对象维护的每个方法。
(2)public virtual IAsyncResult BeginInvoke(int x, int y, AsyncCallback callback, object @object);以异步方式调用委托对象维护的方法。
(3)public virtual int EndInvoke(IAsyncResult result);
4.委托还可以指向包含任意数量out或ref参数(以及用params关键字标记的数组参数)的方法。
5.System.MulticastDelegate 和 System.Delegate
我们永远不会直接派生自这些基类,如果我们使用delegate关键字,就间接创建了一个类,这个类“是” MulticastDelegate。
所有委托类型都共有的核心成员:
Method:此属性方法MethodInfo对象,用以表示委托维护的静态方法的详细信息。
Target:如果方法调用是定义在对象级别的(而不是静态方法),Target返回表示委托维护的方法的对象。如果Targe返回值为null,调用的方法是一个静态方法。
Combine():此静态方法给委托维护的列表添加一个方法。在C#中,使用重载+=操作符作为简化符号调用此方法。
GetInvocationList():此方法返回一个System.Delegate类型的数组,其中数组中的每个元素都表示一个可调用的特定方法。
Remove(),RemoveAll():这些静态方法从调用列表中移除一个(或所有)方法,在C#中,Remove()方法可通过使用重载-=操作符来调用。
6.使用委托发送对象状态通知
(1)定义将通知发送给调用者的委托类型。
(2)声明类中每个委托类型的成员变量。
(3)在类上创建辅助函数使调用者能指定由委托成员变量保存的方法。(注册函数)
(4)在类内部的某个方法中调用委托的方法列表。
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication6 { class Program { static void Main(string[] args) { DoSomething d = new DoSomething("打地基"); d.RegisterWorkEngineHandler(WorkEvent); for (int i = 1; i <= 100; i++) { d.Work(i); } Console.ReadKey(); } static void WorkEvent(string msg) { Console.WriteLine(msg); } } class DoSomething { public string WorkName { get; set; } public DoSomething(string workname) { this.WorkName = workname; } public void Work(int step) { this.handler(string.Format("{0}工作:正在做第{1}步事情...", this.WorkName, step)); } // 定义委托类型 public delegate void WorkEngineHandler(string msg); // 声明私有的委托类型变量 private WorkEngineHandler handler; // 注册委托对象 public void RegisterWorkEngineHandler(WorkEngineHandler handler) { this.handler = handler; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication6 { class Program { static void Main(string[] args) { DoSomething d = new DoSomething("打地基"); d.RegisterWorkEngineHandler(WorkEvent); for (int i = 1; i <= 100; i++) { d.Work(i); } Console.ReadKey(); } static void WorkEvent(string msg) { Console.WriteLine(msg); } } class DoSomething { public string WorkName { get; set; } public DoSomething(string workname) { this.WorkName = workname; } public void Work(int step) { this.handler(string.Format("{0}工作:正在做第{1}步事情...", this.WorkName, step)); } // 定义委托类型 public delegate void WorkEngineHandler(string msg); // 声明私有的委托类型变量 private WorkEngineHandler handler; // 注册委托对象 public void RegisterWorkEngineHandler(WorkEngineHandler handler) { this.handler = handler; } } }
7.方法组转换语法:该特性允许我们在调用以委托作为参数的方法时直接提供方法的名称,而不是创建委托对象。
8.委托协变:因为委托是安全类型,它们不遵守继承的基本规则。协变允许我们构建一个委托,能指向返回类及相关继承体系的方法。
逆变:允许我们创建一个委托,指向多个方法,方法的参数是存在传统继承关系的对象。
9.泛型委托:通过类型参数来构建
public delegate void MyGenericDelegate<T>(T arg);
在不使用泛型的情况下模拟泛型委托:public delegate void MyDelegate(object arg);尽管这样可以把任何类型的数据发送到委托目标,但是会因此
失去类型安全并且可能还会有装箱/拆箱损失
二、事件 event
从头使用委托会有一些重复代码:定义委托,声明必要的成员变量以及创建自定义的注册/注销方法来保存封装等。
1.为了简化自定义方法的构建来为委托调用列表增加和删除方法,C#提供了event关键字。在编译器处理event关键字的时候,它会自动提供注册和注销方法以及委托类型
任何必要的成员变量。这些委托成员变量总是声明为私有的,因此不能直接从触发事件的对象访问它们。
2.定义一个事件分为两个步骤:首先,我们需要定义一个委托类型,它包含在事件触发时将要调用的方法。其次,通过C# evnet关键字用相关委托声明这个事件。
3.C#事件事实上会扩展为两个隐藏的公共方法,一个带add_前缀,另一个带remove_前缀,前缀后面是C#事件的名称。
4..Net基础类库底层委托的第一个参数是一个System.Object,第二个参数是派生自System.EventArgs的子类型。System.Object参数表示一个对发送事件的对象的引用,
第二个参数则表示与该事件相关的信息。
5.泛型EventHandler<T>委托
由于很多自定义委托接受object作为第一个参数,EventArgs派生类型作为第二个参数,我们可以通过使用泛型EventHandler<T>类型来进一步简化
public event EventHandler<MyEventArgs> Exploded;
6.按钮的单击事件
// 摘要: // 表示将处理不包含事件数据的事件的方法。 // // 参数: // sender: // 事件源。 // // e: // 不包含任何事件数据的 System.EventArgs。 [Serializable] [ComVisible(true)] public delegate void EventHandler(object sender, EventArgs e); // // 摘要: // 在单击控件时发生。 public event EventHandler Click; this.button1.Click += new System.EventHandler(this.button1_Click); private void button1_Click(object sender, EventArgs e) { }
三、Lambda表达式
1.手工定义一个由委托对象调用的方法显得有点繁琐,现在可以在事件注册时直接将一个委托与一段代码相关联,这种代码的正式名称称为匿名方法。
t.SomeEvent += delegate(参数){};
2.匿名方法不能访问定义方法中的ref或out参数;匿名方法中的本地变量不能与外部方法中的本地变量重名;匿名方法可以访问外部类作用域中的实例变量(或静态变量)。
3.Lambda表达式只是用更简单的方法来写匿名方法,彻底简化了对.Net委托类型的使用。
4.Lambda表达式规则:首先定义一个参数列表(0个或多个),“=>”标记紧随其后,然后就是处理这些参数的语句。