.NET的委托和事件

简介: 1. 委托1.1 概述 委托相当于C语言的函数指针,但与函数指针不同的是委托是强类型的,使用起来更安全也更方便。而且委托还支持多路广播和异步调用。看看下面这个最简单的委托的例子 Code: 点击(此处)折叠或打开 ...

1. 委托
1.1 概述

委托相当于C语言的函数指针,但与函数指针不同的是委托是强类型的,使用起来更安全也更方便。而且委托还支持多路广播和异步调用。看看下面这个最简单的委托的例子
Code:

点击(此处)折叠或打开

  1. namespace Sample
  2. {
  3.     public delegate void CountChangedHandler(int count);

  4.     public class Counter
  5.     {
  6.         private int count = 0;
  7.         public CountChangedHandler CountChangedHandlers;

  8.         public void add()
  9.         {
  10.             count++;
  11.             if (CountChangedHandlers != null)
  12.                 CountChangedHandlers(count);
  13.         }
  14.     }

  15.     class Program
  16.     {
  17.         static void PrintHandler(int count)
  18.         {
  19.             System.Console.WriteLine("count={0}", count);
  20.         }

  21.         static void Main(string[] args)
  22.         {
  23.             Counter counter = new Counter();
  24.             counter.CountChangedHandlers = new CountChangedHandler(PrintHandler);

  25.             counter.add();
  26.             counter.add();
  27.         }
  28.     }
  29. }

Result:
count=1
count=2

每次add()方法被调用时,触法委托被调用。

1.2 委托对象的初始化
委托的对象可以用静态函数也可以用实例函数初始化。比如
  1. counter.CountChangedHandlers = new CountChangedHandler(PrintHandler);
  1. counter.CountChangedHandlers = new CountChangedHandler(objA.PrintHandler);


也可以隐式创建委托对象

  1. counter.CountChangedHandlers = PrintHandler;
  2. counter.CountChangedHandlers = objA.PrintHandler;

1.3
如果回调函数只在一处用到,专门定义一个函数岂不浪费。于是可以用下面的匿名委托。
  1. counter.CountChangedHandlers = delegate(int count)
  2.             {
  3.                 System.Console.WriteLine("count={0}", count);
  4.             };
匿名委托有个很大特点,就是可以访问所在方法的局部变量。
  1. int k = 0;
  2.             counter.CountChangedHandlers = delegate(int count)
  3.             {
  4.                 k++;
  5.                 System.Console.WriteLine("count={0}", count);
  6.             };
如果只看到表象一定迷惑于局部变量k的生命周期,担心匿名委托被调用时可能已经出了声明匿名委托的方法,k会消失。但通过ildasm可以看到,这种情况下,编译器会自动生成一个类,把被匿名委托访问的局部变量(k)变为这个类的实例变量,所有访问局部变量的匿名委托变成这个类的实例方法(b__0))。也就是说此时k已经不是局部变量而是实例变量了。


如果匿名委托没有访问所在函数的局部变量,匿名委托会被自动生成为一个静态的函数(b__0)。



Lambda可以认为是一种特殊语法形式的匿名委托
  1. counter.CountChangedHandlers = i => System.Console.WriteLine("count={0}", i);

1.4 多路广播
委托可以支持多路广播,也就是可以使用“+=”操作符注册多个回调函数。委托可以支持多路广播,也就是可以使用“+=”操作符注册多个回调函数。使用 “+=”或“-=”时不用担心委托变量为null的情况会出现空指针异常。
Code:
  1. static void Main(string[] args)
  2.         {
  3.             Counter counter = new Counter();
  4.             counter.CountChangedHandlers += PrintHandler;//这里也可使用“=”
  5.             counter.CountChangedHandlers += PrintHandler;

  6.             counter.add();
  7.             counter.add();
  8.         }
Result:
count=1
count=1
count=2
count=2
上面把同一个函数注册了2次,所以也就会被调用2次。


使用“-=”操作符可以删除已注册的回调函数。
Code:

  1. static void Main(string[] args)
  2.         {
  3.             Counter counter = new Counter();

  4.             CountChangedHandler h = new CountChangedHandler(PrintHandler);
  5.             counter.CountChangedHandlers += h;
  6.             counter.CountChangedHandlers += h;
  7.             counter.CountChangedHandlers -= h;

  8.             counter.add();
  9.             counter.add();
  10.         }
Result:
count=1
count=2

上面注册了2次,删除了1次,所以最后被调用了1次。

1.5 异步调用 委托
委托还可以被异步调用
Code:
  1. public void add()
  2.         {
  3.             count++;
  4.             if (CountChangedHandlers != null)
  5.             {
  6.                 IAsyncResult iAsyncRslt = CountChangedHandlers.BeginInvoke(count, null, null);
  7.                 System.Console.WriteLine("delegate call begin");
  8.                 CountChangedHandlers.EndInvoke(iAsyncRslt);
  9.                 System.Console.WriteLine("delegate call end");
  10.             }
  11.         }
Result:
delegate call begin
count=1
delegate call end
delegate call begin
count=2
delegate call end

1.6 委托的实现
通过ildasm查看由委托生成的代码,可以发现编译后声明的委托类型变成了一个继承自MulticastDelegate的类,并提供了几个调用委托的方法。




2. 事件
2.1 概述

在常用的事件监听应用场景下,被监听的对象只需要提供监听回调函数的注册和删除方法。而委托对象提供的功能太多,不宜公开,应该通过private修饰符隐藏起来。
比如像下面这样。
Code(修改前):
  1. public CountChangedHandler CountChangedHandlers;
Code(修改后):
  1. private CountChangedHandler CountChangedHandlers;

  2.         public void RegisterCountChangedHandler(CountChangedHandler handler)
  3.         {
  4.             CountChangedHandlers += handler;
  5.         }

  6.         public void UnRegisterCountChangedHandler(CountChangedHandler handler)
  7.         {
  8.             CountChangedHandlers -= handler;
  9.         }

然而如果在每个事件监听应用场景下都这么写不免繁琐,event就是解决这个问题的好办法。只要在原来声明委托对象的地方使用event关键字加以修饰,编译器就会自动做类似上面的事。被event关键字修饰后,在Counter对象外部只能通过"+="和"-="对事件注册或者删除监听回调函数,在Counter对象内部仍然可以像原来一样使用委托对象。
使用event后的完整代码示例如下:
Code:

点击(此处)折叠或打开

  1. namespace Sample
  2. {
  3.     public delegate void CountChangedHandler(int count);

  4.     public class Counter
  5.     {
  6.         private int count = 0;
  7.         public event CountChangedHandler CountChangedHandlers;

  8.         public void add()
  9.         {
  10.             count++;
  11.             if (CountChangedHandlers != null)
  12.                 CountChangedHandlers(count);
  13.         }
  14.     }

  15.     class Program
  16.     {
  17.         static void PrintHandler(int count)
  18.         {
  19.             System.Console.WriteLine("count={0}", count);
  20.         }

  21.         static void Main(string[] args)
  22.         {
  23.             Counter counter = new Counter();
  24.             counter.CountChangedHandlers += new CountChangedHandler(PrintHandler);

  25.             counter.add();
  26.             counter.add();
  27.         }
  28.     }
  29. }


2.2 事件的实现

通过ildasm查看编译后的代码,可以更清晰发现加与不加event的区别。加了event关键字后,委托对象变成了private的,并且自动生成了一个和委托对象同名的事件。
没有event关键字时:
public CountChangedHandler CountChangedHandlers;

 


有event关键字时:
public event CountChangedHandler CountChangedHandlers;

相关文章
|
开发框架 安全 .NET
C# .NET面试系列三:集合、异常、泛型、LINQ、委托、EF!
<h2>集合、异常、泛型、LINQ、委托、EF! #### 1. IList 接口与 List 的区别是什么? IList 接口和 List 类是C#中集合的两个相关但不同的概念。下面是它们的主要区别: <b>IList 接口</b> IList 接口是C#中定义的一个泛型接口,位于 System.Collections 命名空间。它派生自 ICollection 接口,定义了一个可以通过索引访问的有序集合。 ```c# IList 接口包含一系列索引化的属性和方法,允许按索引访问、插入、移除元素等。 由于是接口,它只定义了成员的契约,而不提供具体的实现。类似于 IEnumera
805 2
VB.NET—窗体引起的乌龙事件
VB.NET—窗体引起的乌龙事件
220 0
|
C++
【.Net】使用委托实现被引用的项目向上级项目的消息传递事件
在实际项目过程中,经常可能遇到被引用的项目要向上传递消息,但是又不能通过方法进行返回等操作,这个时候委托就派上用场了。以下使用委托,来实现被引用的项目向上传递消息的小教程,欢迎各位大佬提供建议。1、新增控制台项目(一般在CS架构中会用的比较多,用于跨线程传递消息使用)。此处用一个控制台项目来模拟演示使用委托进行消息事件的传递教程。
278 0
【.Net】使用委托实现被引用的项目向上级项目的消息传递事件
|
物联网 网络性能优化 开发工具
.NET Core 跨平台物联网网开发:设置委托事件(二)
.NET Core 跨平台物联网网开发:设置委托事件(二)
431 1
.NET Core 跨平台物联网网开发:设置委托事件(二)
|
物联网 网络性能优化
阿里云物联网NET Core 客户端 CZGL.AliloTClient:9.自定义 委托事件方法
阿里云物联网NET Core 客户端 CZGL.AliloTClient:9.自定义 委托事件方法
366 1
|
JSON 物联网 网络性能优化
NET Core 跨平台物联网开发 SDK属性、方法、委托、类(四)
NET Core 跨平台物联网开发 SDK属性、方法、委托、类(四)
464 1
|
物联网 网络性能优化
阿里云物联网 .NET Core 客户端 | CZGL.AliIoTClient:9. 自定义委托事件方法
CZGL.AliIoTClient 有7个委托事件,设置了默认的方法。你可以通过下面的方法使用默认的方法绑定到委托事件中。 public void UseDefaultEventHandler() 1)默认的方法 收到服务器下发属性设置时: public void Default_PubPrope...
4306 0
|
物联网 网络性能优化
阿里云物联网 .NET Core 客户端 | CZGL.AliIoTClient:8. 委托事件
CZGL.AliIoTClient 里设置了 7 个委托事件,在程序的不不同生命周期触发。 1)7个委托事件 /// <summary> /// 服务器属性设置 /// </summary> public PublishPropert...
1194 0