Long time without coding,貌似对programming都失去了曾有的一点点sense了,今日有空再细瞄一下.net的委托和事件。

  • Delegate

    • 首先,委托用于引用一类具有相同返回值和参数列表的方法(可以引用静态方法或者是实例方法),类似于函数指针,用于实现函数回调。
      例如,我们如下声明了委托ProgressChangedDelegate,用于引用参数是int,返回void的方法。

          /// <summary>
          /// 进度改变通知委托
          /// </summary>
          /// <param name="value">当前进度(0~100)</param>
          public delegate void ProgressChangedDelegate(int value);
    • 委托是类型安全的,且委托就是一个类型,它是对特定签名(返回值和参数列表)的方法的包装。
      看一下上面代码生成的IL代码,编译器其实在后面帮我们生成了一个ProgressChangedDelegate的类型,
      且它是继承自类型
      System.MulticastDelegate(多播委托)

    • 委托类型的实例化。下面分别用静态方法和实例方法来实例化委托对象。
                 //declare and initial a delegate with static method
                  ProgressConsoleDisplayer progressConsoleDisplayer = new ProgressConsoleDisplayer();
                  ProgressChangedDelegate progressDelegate
                      = new ProgressChangedDelegate(ProgressConsoleDisplayer.DisplayProgress);
      
               //delegate refer to instance method
                  ProgressFileDisplayer fileDisplayer = new ProgressFileDisplayer(@"c:\progress.log");
                  ProgressChangedDelegate anotherProgressDelegate = new ProgressChangedDelegate(fileDisplayer.DisplayProgress);

      分析IL,C#简洁语法的背后,委托的实例化是通过传入方法指针和方法执行的对象的引用(静态方法是null)来执行构造函数来完成的。

    • 委托的调用。委托实例调用的方式跟调用它引用的方法一样。
       
      progressDelegate(progressValue); anotherProgressDelegate(progressValue);

      分析IL,C#自然的调用语法后面其实是调用委托对象的invoke方法(最后是调用所引用的MethodInfo的Invoke)。

       

    • 既然delegate关键字声明的委托是多播委托,我们可以使用“+=”,“-=”操作符对两个委托对象进行捆绑和解除捆绑。
      通过IL可以看到,这两个操作符的背后其实是调用Delegate类型的Combine和Remove两个静态方法来实现的。
  • Delegate Chain.

    上面说的delegate声明的委托多播委托,也就是说它可以绑定多个方法进行回调。如下代码,进度的变更会依次显示到控制台和记录到文本。

    ///
    
        /// 进度改变通知委托
        /// 
    
        /// 当前进度(0~100)
        public delegate void ProgressChangedDelegate(int value);
    
        class Program
        {
            static void Main(string[] args)
            {
                //declare and initial a delegate with static method
                ProgressConsoleDisplayer progressConsoleDisplayer = new ProgressConsoleDisplayer();
                ProgressChangedDelegate progressDelegate
                    = new ProgressChangedDelegate(ProgressConsoleDisplayer.DisplayProgress);
    
                //delegate refer to instance method
                ProgressFileDisplayer fileDisplayer = new ProgressFileDisplayer(@"c:\progress.log");
                progressDelegate += fileDisplayer.DisplayProgress;
    
                for (int progressValue = 0; progressValue
        /// 进度控制台输出器
        /// 
    
        public class ProgressConsoleDisplayer
        {
            public static void DisplayProgress(int value)
            {
                Console.WriteLine(string.Format("Console Display:Current Progress is {0}", value));
            }
        }
    
        ///
    
        /// 进度文件输出器
        /// 
    
        public class ProgressFileDisplayer:IDisposable
        {
            private readonly FileStream fileStream=null;
            private readonly StreamWriter writer = null;
    
            public ProgressFileDisplayer(string filePath)
            {
                FileStream fileStream=File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
                writer = new StreamWriter(fileStream);
            }
    
            public void DisplayProgress(int value)
            {
                writer.WriteLine(string.Format("File Display:Current Progress is {0}", value));
                writer.Flush();
            }
    
            public void Dispose()
            {
                writer.Dispose();
                fileStream.Dispose();
            }
        }
    

    1.委托链是通过Delegate.Combine和Remove两个静态方法来进行绑定和删除操作的,每次Combine和Remove以后委托都会指向一个新的对象。

    2.委托的调用。多个委托对象combine后会放到内部的集合(数组)中保存,当调用时会按顺序调用数组中的每个元素,调用返回的结果是最后一个委托调用返回的结果。
     delegate int ReturnIntDelegate();
            public static void Run()
            {
                ReturnIntDelegate delegateInstance = () => { return 1; };
                delegateInstance += () => { return 2; };
                delegateInstance += () => { return 3; };
    
                int result = delegateInstance();
                Console.WriteLine(string.Format("delegate call result:{0}", result));
            }
    


    委托链中委托的调用时简单的顺序调用,如果委托数组中的一个委托调用发生未处理的异常则后续的委托都不能调用,为避免这个问题,当然希望在每个委托调用时加入异常处理。又或者,我们希望每个委托在单独的线程上同时执行以避免阻塞,提高性能。总之,基于稳定和性能考虑,我们当然希望可以手工控制每个委托的调用。


    使用MulticastDelegate类的GetInvocationList实例方法可以获取Delegate[]对象,通过遍历数组,控制每个Delegate委托的调用。下面修改上面的代码,加入异常处理。

     delegate int ReturnIntDelegate();
            public static void Run()
            {
                ReturnIntDelegate delegateInstance = () => { return 1; };
                delegateInstance += () =>
                {
                    throw new Exception();
                    return 2;
                };
                delegateInstance += () => { return 3; };
    
                //int result = delegateInstance();
               int result = 0;
               Delegate[] delegates= delegateInstance.GetInvocationList();
                if (delegates != null)
                {
                    foreach (ReturnIntDelegate del in delegates)
                    {
                        try
                        {
                            result=del();
                        }
                        catch (Exception ex)
                        {
                            //just ignore
                        }
                    }
                }
    
                Console.WriteLine(string.Format("delegate call result:{0}", result));
            }
    

  • Event

    事件机制是观察者模式的实现,当对象(Project)的状态改变,一个或者多个观察者(observer)会得到通知并做相应的更新。

    1. 在观察者模式中,观察者继承的基类或者接口都包含了一个方法供被观察对象来调用发送消息,这个方法其实
      就是消息的契约,在事件机制中对应的就是委托,所以我们声明事件成员时需要指定事件的委托类型。
      例如,我们在一个作业类Job中声明一个任务失败MissionFailed事件。

       public class Task
          {
              /// <summary>
              /// occur when task runs failed
              /// </summary>
              public event EventHandler MissionFailed;
      
          }

      这里我使用的是.net库中的委托EventHandler,为遵守建议的命名规范,即使是自定义事件的委托,名称应该以事件名称开始,以EventHandler为后缀。
      被观察者应该仅发送消息,而不会关心观察者的处理消息细节,所以这个标识消息的委托应该是无返回值的。
      可以看一下EventHandler及其泛型版本的定义:

    2. 事件消息的内容体现在委托的方法参数中,一般包含消息的发送者对象和额外参数对象,包含额外参数的类
      按照规范应该以继承自EventArgs类,且名称以EventArgs结尾。
      如我们定义任务失败事件的额外信息类,包含了错误信息字段。

         
      public class MissionFailedEventArgs:EventArgs { public MissionFailedEventArgs(string error) { Message = error; } /// <summary> /// Error Info /// </summary> public string Message { get; private set; } }

      相应修改事件声明:

       public event EventHandler<MissionFailedEventArgs> MissionFailed;
    3. 观察者模式中被观察者内部维护了一个观察者的列表,当观察者订阅通知时会被添加到此列表。当被观察者发送消息时,它会遍历列表,执行方法发送消息。
      在事件中,这个是通过委托的多播来实现的。
      首先,事件成员的本质一组对应于订阅事件和取消订阅事件的方法组。(我们可以对这两个方法进行自定义来实现事件的定制)


      查看IL代码,事件的默认实现中编译器会自动为我们生成一个可访问性为private的跟事件同名的委托成员,还有add_[EventName]和remove_[EventName]
      两个方法,而事件的订阅+=和取消订阅-=就是调用这两个方法来实现,而这两个方法的具体实现最终还是落到Delegate的Combine和Remove操作。
      所以说.net的事件是基于委托的,是委托的封装。

    4. 触发事件。一般会在类中定义一个名为On[EventName]的Protected虚函数来引发事件。

              /// <summary>
              /// Fired the MissionFailed Event
              /// </summary>
              protected virtual void OnMissionFailed(MissionFailedEventArgs e)
              {
                  if (MissionFailed != null) //this should not be thread-safe
                  {
                      MissionFailed(this, e);
                  }
              }
      

      然后在需要的地方调用上面的方法触发事件,发送通知,如下在任务执行发生异常时触发事件。

              /// <summary>
              /// Run Task
              /// </summary>
              public void Run()
              {
                  try
                  {
                  }
                  catch (Exception ex)
                  {
                      OnMissionFailed(new MissionFailedEventArgs(e.Message));
                      throw;
                  }
              }
      

      可以猜测事件触发,根本上应该是委托的调用,查看IL代码可以证实。

    5. 事件的自定义实现。事件成员本质是一组方法,编译器会自动生成默认的实现代码,但我们也可以自定义Add,Remove方法显示定义事件。
      可以参考window Form中的事件定义,由于事件默认的隐式定义会自动为每个事件成员定义一个委托成员,而如果事件没有被使用,会造成
      内存空间的浪费。所以Form中的事件实现是通过字典的形式来操作委托:
             public  class BusinessComponent:Component
          {
              private static object EVENT_BUSINESSSTART = new object();
              private static object EVENT_BUSINESSEND = new object();
      
              /// <summary>
              /// 业务开始事件
              /// </summary>
              public event EventHandler BusinessStart
              {
                  add { Events.AddHandler(EVENT_BUSINESSSTART, value); }
                  remove { Events.RemoveHandler(EVENT_BUSINESSSTART, value); }
              }
      
              /// <summary>
              /// 业务终止事件
              /// </summary>
              public event EventHandler BusinessEnd
              {
                  add { Events.AddHandler(EVENT_BUSINESSEND, value); }
                  remove { Events.RemoveHandler(EVENT_BUSINESSEND, value); }
              }
      
          }

      下面尝试过滤某些事件订阅,同一个事件处理方法仅允许订阅一次。通过遍历委托调用列表,检查是否已经存在相同的委托。

             private EventHandler<MissionFailedEventArgs> missionFailed;
              /// <summary>
              /// occur when task runs failed
              /// </summary>
              public event EventHandler<MissionFailedEventArgs> MissionFailed
              {
                  add
                  {
                      Delegate[] delegates = null;
                      if (missionFailed != null && (delegates = missionFailed.GetInvocationList()) != null)
                      {
                          foreach (Delegate d in delegates)
                          {
                              if (d.Equals(value))
                                  return;
                          }
                      }
                      missionFailed+= value;
                  }
                  remove { }
              }

      调用代码对事件使用同一方法订阅了两次。

              private static void TaskMissionFailedHandler(object sender, MissionFailedEventArgs e)
              {
                  Console.WriteLine(string.Format("Time:{0},task run.", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
              }
              static void Main(string[] args)
              {
      
                  Task task = new Task();
                  task.MissionFailed += TaskMissionFailedHandler;
                  task.MissionFailed += TaskMissionFailedHandler;
                  task.Run();
                  Console.Read();    }  

      但只有一次订阅成功。


References:

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/
http://csharpindepth.com/Articles/chapter2/events.aspx
http://www.oodesign.com/observer-pattern.html


Delegate & Event的更多相关文章

  1. C# delegate event func action 匿名方法 lambda表达式

    delegate event action func 匿名方法 lambda表达式 delegate类似c++的函数指针,但是是类型安全的,可以指向多个函数, public delegate void ...

  2. [UE4] C++实现Delegate Event实例(例子、example、sample)

    转自:http://aigo.iteye.com/blog/2301010 虽然官方doc上说Event的Binding方式跟Multi-Cast用法完全一样,Multi-Cast论坛上也有很多例子, ...

  3. [C#] Delegate, Multicase delegate, Event

    声明:这篇博客翻译自:https://www.codeproject.com/Articles/1061085/Delegates-Multicast-delegates-and-Events-in- ...

  4. C# delegate & event

    public delegate void MyDelegate(string mydelegate);//声明一个delegate对象 //实现有相同参数和返回值的函数        public v ...

  5. Delegate&Event

    Delegate 1.基本类: public class Student { public int Id { get; set; } public string Name { get; set; } ...

  6. Delegate event 委托事件---两个From窗体使用委托事件

    窗体如下:   public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void b ...

  7. 委托与事件--delegate&&event

    委托 访问修饰符 delegate 返回值 委托名(参数); public delegate void NoReturnNoPara(); public void NoReturnNoParaMeth ...

  8. delegate, event - 里面涉及的参数类型必须完全一致,子类是不行的

    public void TestF() { Test += Fun; } public void Fun(Person p) { }  // 如 Person变成 SubPerson,则报错..pub ...

  9. ue4 delegate event

    官网相关 https://docs.unrealengine.com/latest/CHN/Programming/UnrealArchitecture/Delegates/index.html wi ...

随机推荐

  1. UVALive - 3942 (字典树)

    递推:$d(i) $表示从第$i$个字符开始到末尾的字符串(即后缀S[i...n])的分解方案数,则$d(i) = \sum {d(i + len(x))} $,其中字符串$x$代表S[i...n]的 ...

  2. CopyOnWriteArrayList与Collections.synchronizedList的性能对比(转)

    列表实现有ArrayList.Vector.CopyOnWriteArrayList.Collections.synchronizedList(list)四种方式. 1 ArrayList Array ...

  3. 编译原理中DFA最小化

    关于编译原理最小化的操作,专业术语请移步至:http://www.360doc.com/content/18/0601/21/11962419_758841916.shtml 这里只是记录一下个人的理 ...

  4. Keil中查看.c和.h文件的路径

    方法一: 选择任意一个文件,然后点击右键,选择"Option for File xxx"即可查看该文件的路径. 方法二: 单击任意一个文件,然后点击右键,选择"Open ...

  5. redis -memcahe

    tomcat自动化集成 https://blog.51cto.com/ellenv/1932817 Redis与Memcache对比:1.Memcache是一个分布式的内存对象缓存系统而redis是可 ...

  6. Pandas Series和DataFrame的基本概念

    1,创建Series 1.1,通过iterable创建Series Series接收参数是Iterable,不能是Iterator pd.Series(Iterable) 可以多加一个index参数, ...

  7. c# 多线程简化

    编译器自动推断出ParameterizedThreadStart委托,因为Go方法接收一个单独的object参数,就像这样写: 1 2 Thread t = new Thread (new Param ...

  8. C博客作业01——分支、顺序结构

    1.本章学习总结 1.1思维导图 本章学习体会及代码量学习体会 1.2.1学习体会 在暑假的时候就有加入新生学习群,对C语言有一定的基础,所以这周的学习相对轻松,但一些细节方面的知识并不是很了解.在这 ...

  9. 局域网内Ping不通

    局域网ping不通, 原来不可忽视这步......... 通常,经常在局域网里面,为了检测网络是否顺畅,都会ping一下IP,如果网络正常,就可以上网或者远程处理其他故障.但是会出现ping别人的主机 ...

  10. Scratch安装使用教程

    一.说明 一直听说scratch是一款麻省理工所开发的很好的少儿编程学习工具,一直不是很清楚所谓少儿编程是长什么样所以探究了一下. 二.安装 scratch当前到了3.0版本,3.0版本默认直接是we ...