• 深刻理解:C#中的委托、事件


    C#中的事件还真是有点绕啊,以前用JavaScript的我,理解起来还真是废了好大劲!刚开始还真有点想不明白为什么这么绕,想想和JS的区别,最后终于恍然大悟!

    C#中事件绕的根本原因:

    1. C#的方法,它不是一个类型,它只是其它类型的成员;
    2. C#是一个强类型的语言,定义方法时,它的参数必须指定类型,如 public void add(int n){...};

    所以,一个方法不能直接作为其它方法的参数,把一个方法名作为参数,无法指定类型啊,会报错!那我就想啊,既然不能直接传入,那我传入整个对象总可以吧,通过传进来的对象来执行该方法,如下代码:

    using System;
    namespace MyEventTest
    {
        public class SomeClass
        {
            public void Start(int a) { Console.WriteLine("Go:{0}",a); }
        }
        public class Publisher
        {
            public void StartEvent(int a, SomeClass sc)
            {
                if (sc != null)
                {
                    sc.Start(a);  //触发回调方法
                }
            }
        }
        public class MainClass
        {
            static void Main()
            {
                SomeClass some = new SomeClass();
                Publisher p = new Publisher();
                p.StartEvent(5,some); //Go:5
            }
        }
    }

    以上方法确实可以,但C#不完全是这样实现事件的,因为方法的特殊性,C#引入了委托的概念,让委托对象来代表方法作为其它方法的参数;而事件对象,其实就是一个委托对象。下面先介绍一下委托:

    委托

    对应于以上方法:

    public void Start(int a) { Console.WriteLine("Go:{0}",a); } 

    我们可以定义一个委托类型:

    public delegate void MyDel(int a);

    委托的定义:

    1. 委托MyDel它是一个类型,类型名就是MyDel;定义委托相当于定义一个新类,委托在后台实现为派生自System.Delegate类。
    2. 定义委托,就是告诉编译器该委托将表示哪种方法(返回值类型+方法签名),该方法可以是任意类型的实例方法、静态方法,只要方法的签名、返回值类型与委托匹配,那么该委托的实例就可以引用这些方法。
    3. 使用委托,必须创建该委托的实例,并为它指定要引用的方法,如: MyDel d = some.Start;注意这里不是 some.Start() ;
    4. 委托对象支持"+","+="来为它添加更多的方法引用,而"-","-="则是删除引用;
    5. 引用了多个方法的委托就叫多播委托,多播委托派生自基类 System.MulticastDelegate 类,它是 System.Delegate 类的子类
    6. 注意:只要委托对象还存在对方法的引用,它就一直占用内存哦!我想可以用 d = null; 来释放委托对象d;
    7. 可以对委托对象执行调用,如: d(5); 它将把调用传递给它所引用的方法 some.Start(5); ,对于多播委托,它将按顺序调用它引用的所有方法,但如果其中一个方法抛出异常,且没在方法内部处理,则将会将异常往外抛出,之后的方法调用将终止。

      使用委托的规则:

    8. 委托是和类一个级别的,可以在能定义类的任何地方定义委托;
    9. 委托不支持继承;
    10. 可以为委托类型定义任意常见的访问修饰符;
    11. 委托对象所引用的方法也可以是匿名方法、Lambda 表达式;
    12. 多播委托的返回值类型必须是void,否则就只能得到委托调用的最后一个方法的结果。
    13. 在.NET 4.0中,委托开始支持协变与逆变,这样一来,定义委托类型时的签名可以和所要引用的方法的签名不完全匹配(不同类型之间必须是派生关系)
    14. 委托支持泛型,.NET预定义了两个泛型版本的委托:
      • Action< T > 委托表示引用一个返回值类型为void的方法,根据参数个数存在不同的变体版本;如: Action<in T1, in T2>
      • Func< T > 委托表示引用一个带返回值类型的方法,根据参数个数存在不同的变体版本;如: Func<in T1, out TResult> 1个参数T1和返回值类型TResult。

    事件

    说完了委托的概念,就可以继续讲事件了,因为事件是基于委托的!

    事件的概念:

    • 类或对象可以通过事件向其他类或对象通知发生的相关事情。
    • 发送事件的类称为“发行者”,接收事件的类称为“订阅者”。(就是设计模式中的订阅发布者模式);
    • 一个事件可以有多个订阅者。 一个订阅者可处理来自多个发行者的多个事件。如果一个事件有多个订阅者,当引发该事件时,会同步调用多个事件处理程序。也可异步调用。

    .NET Framework 类库中的所有事件均基于 EventHandler 委托,还有泛型版本 EventHandler<EventArgs> ,这个委托是.NET预定义的,不需要我们定义,可以直接用它来实例化一个事件对象,定义如下:

    参数object sender对象是对发布者的实例的引用,EventArgs e对象主要用来存储事件数据

    public delegate void EventHandler(object sender, EventArgs e); //EventArgs主要用来存储事件数据

    public delegate void EventHandler&lt;TEventArgs&gt;(object sender, EventArgs e);

    虽然在自定义的类中的事件可基于任何有效委托类型,但是,通常建议使用.NET预定义事件委托类型让事件基于 .NET 标准事件模式

    下面是我总结的发布基于 .NET 标准事件模式的4个步骤:

    第1步:在发布者类中实例化委托事件,并定义一个实例方法,用来调用委托事件(因为委托事件只能通过定义它的类的实例来调用)。

    定义发布者类之前可先定义一个用来存储事件数据的类(它必须派生于 EventArgs 基类),如下:

    注意:在方法StartEvent()中,声明了一个变量,来保存事件对象的副本,这样在取得事件对象的副本后,到触发事件时,这段时间内,这个事件副本就不会受其它线程的影响。如:在此期间,其它线程注销了回调方法,那么MyEvent就为null了,这时再触发事件将引发错误。(这就是线程安全的事件,当然还可以通过锁机制,或者为事件对象始终引用一个空方法)

    public class MyEventArgs: EventArgs  //定义存储事件数据的类
    {
        public int Current{get;set;}
    }
    public class Publisher
    {
        public event EventHandler<MyEventArgs> MyEvent; //第1步:实例化委托事件
        public int Sum{get;set;}
        public void StartEvent(int a)  
        {
              var EventCopy = MyEvent; //每次都取一个副本
              MyEventArgs args = new MyEventArgs();
              args.Current = a;
              this.Sum += a;
              if (EventCopy != null)
              {
                   EventCopy(this,args);  //调用事件
              }
        }
    }

    第2步:定义订阅者类,在该类中定义和委托事件相匹配的方法(事件触发时,实际要执行的方法)

    public class Subscriber
    {
         public void Dosomething1(object obj, MyEventArgs e)
         {
                Publisher p = (Publisher)obj;
                Console.WriteLine("Meg: Sum = {0}, Current = {1}", p.sum, e.Current);
         }
         public void Dosomething2(object obj, MyEventArgs e)
         {
         }
    }

    第3步:在客户端代码中,在发布者类的实例上为委托事件注册回调方法

    public class MainClass
    {
        static void Main()
        {
             Publisher p = new Publisher{ Sum = 0 };
             Subscriber sub = new Subscriber();
             p.MyEvent += sub. Dosomething1;  //注册回调方法
             p.MyEvent += sub. Dosomething2;
             
             p. StartEvent( 5 ); //调用方法,间接触发事件
    
             p.MyEvent -= sub. Dosomething1;  //取消注册
        }
    }

    要点:事件对象其实就是一个委托对象,把事件当委托来看,就比较容易理解了!不要被Event这个单词给蒙蔽了!

    介绍完了!下回将介绍C#中的其它一些较难理解的概念!

    https://www.cnblogs.com/sjqq/p/6917564.html

  • 相关阅读:
    试题 历届试题 国王的烦恼
    试题 历届试题 九宫重排
    试题 历届试题 网络寻路
    试题 历届试题 危险系数
    试题 历届试题 横向打印二叉树
    试题 历届试题 幸运数
    试题 历届试题 大臣的旅费
    试题 历届试题 连号区间数
    Linux多进行之fork
    linux C语言getopt()函数的使用
  • 原文地址:https://www.cnblogs.com/asdyzh/p/9870798.html
Copyright © 2020-2023  润新知