声明:这篇博客翻译自:https://www.codeproject.com/Articles/1061085/Delegates-Multicast-delegates-and-Events-in-Csharp网络
第一次翻译英文博客,因为水平(技术水平+英语理解能力)有限/不足,确定会有所疏漏/错误,请及时指正。架构
在网络上搜索一下关于C#代理,事件,多播代理的文章,不少不少。不过仍是有些地方讲的不明白/透彻。这篇博客将以简单易懂的方式来说解这3个概念。下面先回答一下:app
下面的图解解释了delegate,multicase delegate, event之间的联系。函数
在本篇文章的剩余部分,咱们未来更详细的理解上面这些话。spa
“Delegate是方法的指针”,你能够经过delegate来调用指向的方法。翻译
经过下面3步建立delegate:代理
声明delegate,PS: 在这一步你将使用delegate关键字来声明委托,注意必须保证delegate的签名和须要指向的函数/方法签名同样/一致。例以下面代码中“SomeMethod()"无返回值且无输入参数。所以示例代码中的"SomeMethodPtr()"delegate定义是合适的;指针
建立一个delegate对象,PS: 当你建立了一个delegate以后,须要建立一个delegate的对象才能使用它,参考代码中注释Step2;code
调用delegate,PS: 经过调用delegate的Invoke方法调用"SomeMethod".orm
delegate void SomeMethodPtr(); // 1. Declare delegate static void Main(string[] args) { SomeMethodPtr ptrObject = SomeMethod; // 2. Create object of delegate ptrObject.Invoke(); // 3. Invoke the delegate } static void SomeMethod() { // some code }
目前咱们理解了delegate是方法/函数的指针。经过delegate间接调用一个方法有什么好处呢?
当咱们须要调用的code在另一个程序集中,例以下面的文件搜索类在一个其余assebmly中。PS:这个文件搜索代码只是一个模拟代码。
public class SearchFile { public void Search() { // File search is happening for(int i=0;i<100;i++) { string str = "File " + i; } } }
如今咱们在另一个单独的Console应用中调用上面的代码,当搜索完毕后,马上通知UI并显示搜索的文件名。
static void Main(string[] args) { SearchFile fl = new SearchFile(); fl.Search(); }
换句话所咱们须要一个CALLBACK(回调函数)当File Search结束后通知Console程序。这就是为何说delegate是用来作回调函数的。
不少开发者想为何不在Searh()方法中直接调用“Console.Write”进行输出呢。这样作会将UI技术和核心代码有耦合。若是咱们把这段代码给WPF/WinForms程序调用,Console.Write作UI输出不适用.
public class SearchFile { public void Search() { // File search is happening for (int i = 0; i < 100; i++) { string str = "File" + i; Console.Write(str); } } }
使用delegate实现一个回调函数
在SearchFile类中实现delegate,第一件事是暴露一个delegate的调用。在下面的SearchFile类中,定义一个WheretoCall的delegate,仔细观察Step1,2中的代码,此时的WheretoCall是空的,任何一个客户端程序想要有一个回调事件,传递一个签名一致的方法便可。
public class SearchFile { public delegate void WheretoCall(string status); // Step1 public WheretoCall wheretoCall = null; // Step 2 public void Search() { // File search is happening for (int i = 0; i < 100; i++) { string str = "File" + i; wheretoCall(str); // Step 3 } } }
如今调用上述代码时只须要传递一个方法的引用到delegate指针便可。请看下面代码中Step 1
static void Main(string[] args) { SearchFile fl = new SearchFile(); fl.wheretoCall = CallHere; // Step 1 fl.Search(); } static void CallHere(string message) { Console.Write(message); }
上面图示中SearchFile"类发送通知(广播)到3个订阅方法。咱们能够把这种模式称为订阅发布架构。SearchFile类是发布者。
static void CallHereToWriteToFile(string message) { System.IO.File.WriteAllText(@"c:\somtext.txt", message); } static void CallHereForConsole(string message) { Console.WriteLine(message); } static void CallHereToWriteInternally(string message) { messages.Add(message); }
为了实现上述目标,咱们不须要修改SearchFile类,在客户端调用时使用“+=”将方法分配给“wheretoCall”代理。若是你不想订阅,使用"-="取消订阅便可。
static void Main(string[] args) { SearchFile fl = new SearchFile(); fl.wheretoCall += CallHereForConsole; fl.wheretoCall += CallHereToWriteToFile; fl.wheretoCall += CallHereToWriteInternally; fl.Search(); }
经过multicast delegate实现的订阅-发布者模式有一个严重的问题,订阅者能够修改这个delegate。在一个真实的订阅-发布者模式/广播模式中,订阅者只能订阅/取消订阅。
下面的代码中,订阅者能够将发布者delegete设置为NULL,而且能够自由调用delegate。
static void Main(string[] args) { SearchFile f1 = new SearchFile(); f1.wheretoCall += CallHereForConsole; f1.wheretoCall.Invoke("status"); // The client can invoke. f1.wheretoCall = null; // The delegate can be modified. f1.wheretoCall += CallHereToWriteInternally; f1.Search(); }
下面使用event关键字封装delegate,
public class SearchFile { public delegate void WheretoCall(string status); // Step1 public event WheretoCall wheretoCall = null; // Step 2 public void Search() { // File search is happening for (int i = 0; i < 100; i++) { string str = "File" + i; wheretoCall(str); // Step 3 } } }
编译客户端代码,出现以下错误:
这个错误的意思是对于一个Event事件你只能订阅(+=)或者取消订阅(-=)。