奥东......C# 委托 事件
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
委托不同于string的一个特性:可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法
使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DelegateDemo { class DelegateDemoOne { public delegate void GreetingDelegate(string name); public static void GreetPeople(string name, GreetingDelegate MakeGreeting) { MakeGreeting(name); } public static void EnglishGreeting(string name) { Console.WriteLine("Morning, " + name); } public static void ChineseGreeting(string name) { Console.WriteLine("早上好, " + name); } static void Main(string[] args) { //第一种方式 //GreetPeople("English man", EnglishGreeting); //GreetPeople("Chinese man", ChineseGreeting); //第二种方式 //GreetingDelegate delegate1, delegate2; //delegate1 = EnglishGreeting; //delegate2 = ChineseGreeting; //GreetPeople("English", delegate1); //GreetPeople("Chinese", delegate2); //第三种方式 //注意这里,第一次用的“=”,是赋值的语法;第二次,用的是“+=”,是绑定的语法。如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误 //GreetingDelegate delegate1; //delegate1 = EnglishGreeting; // 先给委托类型的变量赋值 //delegate1 += ChineseGreeting;// 给此委托变量再绑定一个方法 // 将先后调用 EnglishGreeting 与 ChineseGreeting 方法 //GreetPeople("English", delegate1); GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting); delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法 也进行结束绑定 GreetPeople("new实例化 ", delegate1); Console.ReadKey(); } } }
面向对象化设计
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleDemo { public delegate void GreetingDelegate(string name); //新建的GreetingManager类 public class GreetingManager { //在GreetingManager类的内部声明delegate1变量 public GreetingDelegate delegate1; //为了解决那种自己调用自己还传递自己的写法 //public void GreetPeople(string name, GreetingDelegate MakeGreeting) //{ // MakeGreeting(name); //} //方法改写 传递过来的是委托的名称 public void GreetPeople(string name) { if (delegate1 != null) { //如果有方法注册委托变量 delegate1(name);//通过委托调用方法 } } } class ProgramTwo { private static void EnglishGreeting(string name) { Console.WriteLine("Morning, " + name); } private static void ChineseGreeting(string name) { Console.WriteLine("早上好, " + name); } static void Main(string[] args) { GreetingManager gm = new GreetingManager(); //GreetingDelegate delegate1; //gm.delegate1 = EnglishGreeting; //gm.delegate1 += ChineseGreeting; //这条语句很奇怪,在调用gm.GreetPeople方法的时候,再次传递了gm的delegate1字段 //gm.GreetPeople("Jimmy Zhang", gm.delegate1); //gm.GreetPeople("Jimmy Zhang", EnglishGreeting); //gm.GreetPeople("张子阳", ChineseGreeting); //GreetPeople方法改写之后的调用 gm.delegate1 = EnglishGreeting; gm.delegate1 += ChineseGreeting; gm.GreetPeople("Jimmy Zhang"); //注意,这次不需要再传递 delegate1变量 Console.ReadKey(); } } }
委托为什么不能声明为private:
因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,你把它声明为private了,客户端对它根本就不可见,那它还有什么用?
声明为public类型的好处:
在客户端可以对它进行随意的赋值等操作,严重破坏对象的封装性
如果是String类型看做是委托你怎么办:
答案是使用属性对字段进行封装。
使用委托的时候第一个方法注册用“=”,是赋值语法,因为要进行实例化,第二个方法注册则用的是“+=”的终极体验:
Event,它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。
在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同
在GreetingManager类中添加如下代码
//类中 public event GreetingDelegate MakeGreet; //Main方法中 //gm.MakeGreet = EnglishGreeting;//错误 1 事件“ConsoleDemo.GreetingManager.MakeGreet”只能出现在 += 或 -= 的左边(从类型“ConsoleDemo.GreetingManager”中使用时除外)
private GreetingDelegate MakeGreet; //对事件的声明 实际是 声明一个私有的委托变量 [MethodImpl(MethodImplOptions.Synchronized)] public void add_MakeGreet(GreetingDelegate value){ this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value); } [MethodImpl(MethodImplOptions.Synchronized)] public void remove_MakeGreet(GreetingDelegate value){ this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value); }
MakeGreet事件确实是一个GreetingDelegate类型的委托,只不过不管是不是声明为public,它总是被声明为private
另外,它还有两个方法,分别是add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委托类型的方法和取消注册。
实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应remove_MakeGreet。而这两个方法的访问限制取决于声明事件时的访问限制符
在add_MakeGreet()方法内部,实际上调用了System.Delegate的Combine()静态方法,这个方法用于将当前的变量添加到委托链表中。
我们前面提到过两次,说委托实际上是一个类,在我们定义委托的时候:public delegate void GreetingDelegate(string name);
当编译器遇到这段代码的时候,会生成下面这样一个完整的类:
public sealed class GreetingDelegate:System.MulticastDelegate{ public GreetingDelegate(object @object, IntPtr method); public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object); public virtual void EndInvoke(IAsyncResult result); public virtual void Invoke(string name); }
参考一个小程序理解下事件和委托结合使用
大纲图如下:
myEventSource 3.引发事件的类 事件 在内部触发,外部绑定
myEventArgs - Class 1.提供事件数据的类
myEventHandler - delegate 2.事件委托
myEventListener 监听事件的类
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleDemo { //事件功能由三个互相联系的元素提供的,1.提供事件数据的类,2.事件委托,3.引发事件的类 //发布事件的类 public class myEventSource//3.引发事件的类 { public class myEventArgs : EventArgs //1.提供事件数据的类,来至System.EventArgs { public string KeyToRaiseEvent = "init value"; //添加了一个只读属性KeyToRaiseEvent public myEventArgs(string keyToRaiseEvent) { KeyToRaiseEvent = keyToRaiseEvent; } } //在myEventSource中添加的委托和事件 public delegate void myEventHandler(object sender, myEventArgs e);//2.事件委托 public event myEventHandler myEvent;//声明一个事件 public void OnmyEvent(string keyToRaiseEvent)//引发事件的方法 只有此方法调用的时候才会进行控制台输出 在内部触发,外部绑定 { myEventArgs e = new myEventArgs(keyToRaiseEvent); if (myEvent != null) { myEvent(this, e);//事件的触发 } else { Console.WriteLine("事件初次绑定 eventSource 实例化 myEvent为null"); } } } //监听事件的类 感觉这个类狠多余 public class myEventListener { public void KeyPressed(object sender, myEventSource.myEventArgs e)//处理事件的方法,这个方法必须与委托的签名一致 { Console.WriteLine("发送者为:{0},所按的键为:{1}", sender, e.KeyToRaiseEvent); } //订阅事件 public void Subsribe(myEventSource eventSource) { //把KeyPressed这个方法“绑定”到委托上,然后再通过+=将该委托添加到源对象的事件中 绑定事件处理程序 eventSource.myEvent += new myEventSource.myEventHandler(KeyPressed); } //取消订阅事件 public void UnSubsribe(myEventSource eventSource) { eventSource.myEvent -= new myEventSource.myEventHandler(KeyPressed); } } class Program { static void Main(string[] args) { myEventSource eventSource = new myEventSource();//这里传递的是一个事件源 myEventListener eventListener = new myEventListener(); eventSource.OnmyEvent(""); eventListener.Subsribe(eventSource);//订阅事件 也就是添加委托引用 这里没有任何值 只是添加了引用 eventSource.OnmyEvent(" 空格"); string s = Console.ReadLine();//读入一个字符串 eventSource.OnmyEvent(s);//引发事件 eventListener.UnSubsribe(eventSource); s = Console.ReadLine(); eventSource.OnmyEvent(s); Console.ReadKey(); //运行结果 //a //发送者为:ConsoleApplication1.myEventSource,所按的键为:a //a } } }