zoukankan      html  css  js  c++  java
  • C# 委托 事件

    一:什么叫委托

    通过反射发现,委托其实是一个类,继承自System.MulticastDelegate,但是System.MulticastDelegate这个类是特殊类,不能被继承

    二:委托的声明 

     public delegate void NoReturnNoParaOutClass();
     public class MyDelete
     {
          public delegate void NoReturnNoPara<T>(T t);
          public delegate void NoReturnNoPara();
          public delegate void NoReturnWithPara(int x, int y);
          public delegate int WithReturnNoPara();
          public delegate string WithReturnWithPara(out int x, ref int y);
        }

    委托可以声明在类外面,可以声明再类里面

    三:委托的实例和调用 

          private int GetSomething()
            {
                return 1;
            }
            private int GetSomething2()
            {
                return 2;
            }
    
            private int GetSomething3()
            {
                return 3;
            }
            private void DoNothing()
            {
                Console.WriteLine("This is DoNothing");
            }
            private static void DoNothingStatic()
            {
                Console.WriteLine("This is DoNothingStatic");
            }
            public string ParaReturn(out int x, ref int y)
            {
                throw new Exception();
            }
    View Code
    //多种途径实例化,要求传递一个参数类型,返回值都跟委托一致的方法
    {
       WithReturnWithPara method = new WithReturnWithPara(ParaReturn);
       int x = 0;
       int y = 0;
       var dd = method.Invoke(out x, ref y);
     }
    //begininvoke
    {
       WithReturnNoPara method = new WithReturnNoPara(this.GetSomething);
       int iResult = method.Invoke();
       iResult = method();
       var result = method.BeginInvoke(null, null);//异步调用
       method.EndInvoke(result);
    }
    {
       NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);
       //委托实力的调用,参数和委托约束的一致
        method.Invoke(); //1
        //method(); //2
        //method.BeginInvoke(null, null); //3
        //this.DoNothing(); //1,2,3都等同于this.DoNothing
    
    }
    {
         NoReturnNoPara method = new NoReturnNoPara(DoNothingStatic);
    }
    {
        NoReturnNoPara method = new NoReturnNoPara(Student.StudyAdvanced);
    }
    {
        NoReturnNoPara method = new NoReturnNoPara(new Student().Study);
    }

    四:为什么要使用委托

    有时候我们声明一个方法,直接调用蛮好的,为啥还要使用委托,然后还要先声明,再实例化,再inovke调用呢?

    下面我们举个例子,比如一个人问好这件事情,不同人问候方式不一样,我们会先定义一个类型,如枚举

    public enum PeopleType
     {
            Chinese,
            America,
            Japanese
     }

    然后通过不同的类型来判断问候方式不同,如下

            /// 为不同的人,进行不同的问候
            /// 传递变量--判断一下----执行对应的逻辑
            /// </summary>
            /// <param name="name"></param>
            /// <param name="peopleType"></param>
            public void SayHi(string name, PeopleType peopleType)
            {
                switch (peopleType)
                {
                    case PeopleType.Chinese:
                        Console.WriteLine($"{name}晚上好");
                        break;
                    case PeopleType.America:
                        Console.WriteLine($"{name},good evening");
                        break;
                    case PeopleType.Japanese:
                        Console.WriteLine($"{name},&&%*^^***@@@&&&&");
                        break;
                    default:
                        throw new Exception("wrong peopleType"); //遇到异常报错
                }
            }

    这样做的好处是:以后如果增加公共逻辑等比较容易,但是如果类型比较多,这个方法会变成无限制改动,导致方法难以维护,于是很多人想着增加分支,就增加方法--不影响别的方法的思路来改善

      public void SayHiChinese(string name)
      {
             Console.WriteLine($"{name}晚上好");
      }
      public void SayHiJapanese(string name)
      {
             Console.WriteLine($"{name},&&%*^^***@@@&&&&");
      }
      public void SayHiAmerican(string name)
      {
              Console.WriteLine($"{name},good evening");
      }

    然后上层判断调用

    这样做的好处是:修改某个方法--不影响别的方法 ,但是缺点却是:增加公共逻辑---多个方法就有很多重复代码

    那么我们想:既增加逻辑方便,又维护简单,鱼肉熊掌,如何兼得呢?

    我们可以把相应的逻辑做为参数传进来,这样就解决了我们的问题

    具体我们可以按照以下来做:

      public void SayHiPerfact(string name, SayHiDeletegate method)
      {
          Console.WriteLine("增加开始日志");
          method.Invoke(name);
          Console.WriteLine("增加结束日志");          
      }
      public delegate void SayHiDeletegate(string name);

    然后调用的时候如下:

      SayHiDeletegate method = new SayHiDeletegate(SayHiChinese);

    这样就做到了

    1:逻辑解耦,方便维护 

    2:代码重构,去掉重复

    其实这也是我们选择使用委托的两大优点

    注意:以上我们纯粹为了定义委托而定义委托,其实框架已经我们帮我们定义了Action 和Func这两个委托,Action是没有返回值,Func是有返回值的,这两个委托类已经足够我们使用了,所以有时候我们使用的时候,没有必要自己再去定义,而直接使用即可

     1:Action: 系统提供的,0-16个泛型参数,不带返回值的 委托

    //Action 系统提供的,0-16个泛型参数,不带返回值的 委托
    Action action0 = new Action(DoNothing);
    Action action1 = this.DoNothing; //语法糖,就是编译器帮我们添加上new Action
    Action<int> action2 = this.ShowInt;

    2:Func 系统提供的,0-16个泛型参数,带一个返回值的 委托

     //Func 系统提供的,0-16个泛型参数,带返回值的 委托
     Func<int> func = this.GetSomething2;
     func.Invoke();
    
     Func<int, string> func1 = this.ToString;
     func1.Invoke(1);

    3:系统或者框架为什么要封装这样的方法?第一步我们定义一个这样的方法,需要传入一个Action

    private void DoAction(Action act)
    {
        act.Invoke();
    }

    然后我们可以这样调用:

    Action action0 = this.DoNothing;
    NoReturnNoPara method = this.DoNothing;
    //   this.DoAction(method); //这样就不行,因为类型不一致
    this.DoAction(action0);

    但是我们不能使用 this.DoAction(method),因为:委托的本质是类,action和NoReturnNoPara是不同的类,虽然实例化都可以传递相同的方法,但是没有父子关系,所以不能替换,就像student和teacher两个类,实例化都是传递id/name,但是二者不能替换

    所以:框架提供这种封装,自然是希望大家都统一使用Action/Func,但是之前还有很多委托,至今再框架中还能看到,因为.net向前兼容,以前的版本去不掉的,保留着,这是历史包袱,此后我们就不要定义新的委托了!

    五:多播委托

    委托是一个类,然后继承于MulticastDelegate(多播委托),但是这个类是特殊类,所以任何一个委托都是多播委托类型的子类

     1 {
     2     //多播委托:任何一个委托都是多播委托类型的子类,可以通过+=去添加方法
     3     //多播委托如果中间出现未捕获的异常,方法链直接结束
     4     //多播委托:一个变量保存多个方法,可以增减;invoke的时候可以按顺序执行
     5     //+= 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行系列方法
     6     Student studentNew = new Student();
     7 
     8     NoReturnNoPara method = new NoReturnNoPara(this.DoNothing); //普通的DoNothing方法
     9     method += new NoReturnNoPara(this.DoNothing); //普通的DoNothing方法
    10     method += new NoReturnNoPara(DoNothingStatic); //静态方法
    11     method += new NoReturnNoPara(Student.StudyAdvanced); //静态方法
    12     method += new NoReturnNoPara(new Student().Study);//不是同一个实例,所以是不同的方法
    13     method += new NoReturnNoPara(studentNew.Study);
    14     method.Invoke();
    15     //method.BeginInvoke(null, null);//委托里面如果有多个Target,则不能异步调用,多播委托是不能异步的
    16 
    17     foreach (Action item in method.GetInvocationList()) //得到委托实例的target方法 ,静态方法target为null
    18     {
    19         item.Invoke();
    20         //item.BeginInvoke(null, null);
    21     }
    22 
    23     //-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常
    24     method -= new NoReturnNoPara(this.DoNothing);
    25     method -= new NoReturnNoPara(DoNothingStatic);
    26     method -= new NoReturnNoPara(Student.StudyAdvanced);
    27     method -= new NoReturnNoPara(new Student().Study); //去不掉,原因是不同的实例的相同方法,并不吻合
    28     method -= new NoReturnNoPara(studentNew.Study);
    29     method.Invoke();
    30 }
    31 {
    32     WithReturnNoPara method = new WithReturnNoPara(this.GetSomething);
    33     method += new WithReturnNoPara(this.GetSomething2);
    34     method += new WithReturnNoPara(this.GetSomething3);
    35     int iResult = method.Invoke();//多播委托带返回值,结果以最后的为准,所以一般多播委托用的是不带返回值的
    36 }
    View Code

    注意:

    += 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行系列方法

    -= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合(new Student().Study如果声明两次则不属于吻合)的,移除且只移除一个,没有也不异常

    多播委托其实就是观察者模式的缩影。

    比如一只猫叫了一声,然后需要触发一系列的动作,比如老鼠跑,孩子哭,狗叫等等

    如果我们把这些都写在猫的miao的方法中,如下:

    public void Miao()
    {
        Console.WriteLine("{0} Miao", this.GetType().Name);
    
        new Mouse().Run();
        new Baby().Cry();
        new Mother().Wispher();
        //new Brother().Turn();
        new Father().Roar();
        new Neighbor().Awake();
        new Stealer().Hide();
        new Dog().Wang();
    }

    上面的代码:

    依赖太重,依赖多个类型,任何类型的变化都得修改猫
    职责耦合,猫不仅自己miao,还得找各种对象执行各种动作甚至控制顺序
    任意环节增加或者减少调整顺序, 都得修改猫

    那么我们可以修改代码,然后Cat的这类中,增加一个委托,然后猫只是自己叫,具体的其他的动作,只需要执行这个多播委托即可,代码如下:

     //猫 叫一声   触发一系列后续动作  
     //多了个 指定动作  正是这个不稳定   封装出去   甩锅
     public MiaoDelegate MiaoDelegateHandler;
     public void MiaoNew()
     {
         Console.WriteLine("{0} MiaoNew", this.GetType().Name);
         if (this.MiaoDelegateHandler != null)
         {
             this.MiaoDelegateHandler.Invoke();
         }
     }

    然后外面调用的时候可以直接通过下面:

     {
         Cat cat = new Cat();
         cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run);
         cat.MiaoDelegateHandler += new MiaoDelegate(new Baby().Cry);
         cat.MiaoDelegateHandler += new MiaoDelegate(new Mother().Wispher);
         cat.MiaoDelegateHandler += new MiaoDelegate(new Brother().Turn);
         cat.MiaoDelegateHandler += new MiaoDelegate(new Father().Roar);
         cat.MiaoDelegateHandler += new MiaoDelegate(new Neighbor().Awake);
         cat.MiaoDelegateHandler += new MiaoDelegate(new Stealer().Hide);
         cat.MiaoDelegateHandler += new MiaoDelegate(new Dog().Wang);
         cat.MiaoNew();
         Console.WriteLine("***************************"); 
     }

    这样猫就减少了依赖,而且猫叫后,各种动作的顺序也可以随便改变,而不会改变猫叫的方法!

    六:事件

    事件:是带event关键字的委托的实例,event可以限制变量被外部调用/直接赋值
    event:限制权限,只允许在事件声明类里面去invoke和赋值,不允许外面,甚至子类都能运用

    event事件只能声明在类中,而委托可以声明在类外面

    比如我们在cat类中定义一个事件

     public event MiaoDelegate MiaoDelegateHandlerEvent;
     public void MiaoNewEvent()
     {
         Console.WriteLine("{0} MiaoNewEvent", this.GetType().Name);
         if (this.MiaoDelegateHandlerEvent != null)
         {
             this.MiaoDelegateHandlerEvent.Invoke();
         }
     }

    然后只能在cat类中中进行invoke调用,如果我们定义一个子类继承cat类,如下:

    public class ChildClass : Cat
    {
        public void Show()
        {
            this.MiaoDelegateHandlerEvent += null;
            if (this.MiaoDelegateHandlerEvent != null)//子类也不能调用,调用报错
            {
                this.MiaoDelegateHandlerEvent.Invoke();
            }
        }
    }
    this.MiaoDelegateHandlerEvent.Invoke(); 这样是不能使用的,Invoke这个是完全不能调用的!

    委托和事件的区别与联系?
    委托是一个类型,比如Student
    事件是委托类型的一个实例,加上了event的权限控制 比如学生加菲猫,是一个确切的实体

    然后事件也可以通过下面来调用多个方法

    {
        Cat cat = new Cat();
        //cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run);
        cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Baby().Cry);
        cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Mother().Wispher);
        cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Brother().Turn);
        cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Father().Roar);
        cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Neighbor().Awake);
        cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Stealer().Hide);
        cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Dog().Wang);
        cat.MiaoNewEvent();
        Console.WriteLine("***************************");
    }

    下面写一个标准的的事件,事件一般分为三种:

    1:事件的发布者:发布事件,并且在满足条件时候触发事件

    2:事件的订户:关注事件,事件发生后,自己做出相应的动作

    3:事件的订阅:把订户和发布者的事件关联起来

     1     /// <summary>
     2     /// 委托是一种类型,静态类不能被继承,所以委托不能声明静态,
     3     /// event只是一个实例,所以可以生成实例
     4     /// </summary>
     5     class EventStandard
     6     {
     7         /// <summary>
     8         /// 订阅:把订户和发布者的事件关联起来
     9         /// </summary>
    10         public static void Show()
    11         {
    12             iPhoneX phone = new iPhoneX()
    13             {
    14                 Id = 1234,
    15                 Tag = "1.0"
    16             };
    17             // 订阅:把订户和发布者的事件关联起来
    18             phone.DiscountHandler += new Student().Buy;
    19             phone.DiscountHandler += new Teacher().Notice;
    20 
    21             phone.Price = 10000;
    22 
    23         }
    24         /// <summary>
    25         /// 订户:关注事件,事件发生后,自己做出对应的动作
    26         /// </summary>
    27         public class Student
    28         {
    29             public void Buy(object sender, EventArgs e)
    30             {
    31                 iPhoneX phone = (iPhoneX)sender;
    32                 Console.WriteLine($"this is {phone.Tag} iphoneX");
    33                 XEventArgs args = (XEventArgs)e;
    34                 Console.WriteLine($"之前的价格{args.OldPrice}");
    35                 Console.WriteLine($"限制的价格{args.NewPrice}");
    36                 Console.WriteLine("立马买!!");
    37             }
    38         }
    39         public class Teacher
    40         {
    41             public void Notice(object sender, EventArgs e)
    42             {
    43                 iPhoneX phone = (iPhoneX)sender;
    44                 Console.WriteLine($"this is {phone.Tag} iphoneX");
    45                 XEventArgs args = (XEventArgs)e;
    46                 Console.WriteLine($"之前的价格{args.OldPrice}");
    47                 Console.WriteLine($"限制的价格{args.NewPrice}");
    48                 Console.WriteLine("立马买!!");
    49             }
    50         }
    51 
    52         /// <summary>
    53         /// 事件参数 一般会为特定的事件去封装个参数
    54         ///订户:Teacher/Student:关注事件,事件发生后,自己做出对应的动作
    55         /// </summary>
    56         public class XEventArgs : EventArgs
    57         {
    58             public int OldPrice { set; get; }
    59             public int NewPrice { set; get; }
    60         }
    61 
    62 
    63         /// <summary>
    64         /// 事件的发布者,发布事件,并且在满足条件时候触发事件
    65         /// </summary>
    66         public class iPhoneX
    67         {
    68             public int Id { set; get; }
    69             public string Tag { set; get; }
    70             public int Price
    71             {
    72                 set
    73                 {
    74                     if (value < this._price)
    75                     {
    76                         this.DiscountHandler?.Invoke(this, new XEventArgs() { OldPrice = this._price, NewPrice = value });
    77                         this._price = value;
    78                     }
    79                 }
    80                 get { return this._price; }
    81             }
    82 
    83             private int _price;
    84 
    85             /// <summary>
    86             /// 打折事件
    87             /// 
    88             /// </summary>
    89             public event EventHandler DiscountHandler;
    90         }
    91     }
    View Code

      EventHandler:框架自带,表示将用于处理不具有事件数据的事件的方法。EventHandler(object sender, EventArgs e)

    下面这个例子也很经典

     1   /// <summary>
     2     /// 妈妈做好饭,触发爸爸和儿子一起吃饭,并且妈妈会把对应的菜单同时告知爸爸和儿子
     3     /// </summary>
     4     public class MyEvenStandard
     5     {
     6         public class Meal
     7         {
     8             public static void FinishMeal()
     9             {
    10                 Mom mom = new Mom();
    11                 mom.EatHandler += (new Dad()).Eat;
    12                 mom.EatHandler += (new Son()).Eat;
    13                 mom.Cook();
    14             }
    15         }
    16 
    17         public class Mom
    18         {
    19             //EventHandler(object sender, EventArgs e) 事件的原型,里面sender则是发布者自己,e:是发布者的交互参数
    20             public event EventHandler EatHandler; 
    21             public void Cook()
    22             {
    23                 Console.WriteLine("开始吃饭了");
    24                 EatHandler?.Invoke(this, new MenuArags() { Fruid = "orange", Greens = "green pepper", Meat = "fish" });
    25             }
    26         }
    27         /// <summary>
    28         /// 爸爸
    29         /// </summary>
    30         public class Dad
    31         {
    32             public void Eat(object sender, EventArgs args)
    33             {
    34                 var menArgs = (MenuArags)args;
    35                 Console.WriteLine($"儿子,我们今天晚上次:{menArgs.Greens},{menArgs.Fruid},{menArgs.Meat}");
    36             }
    37         }
    38 
    39         /// <summary>
    40         /// 儿子
    41         /// </summary>
    42         public class Son
    43         {
    44             public void Eat(object sender, EventArgs args)
    45             {
    46                 var menArgs = (MenuArags)args;
    47                 Console.WriteLine($"爸爸,我们今天晚上次:{menArgs.Greens},{menArgs.Fruid},{menArgs.Meat}");
    48             }
    49         }
    50 
    51         public class MenuArags : EventArgs
    52         {
    53             /// <summary>
    54             /// 水果
    55             /// </summary>
    56             public string Fruid { set; get; }
    57 
    58             /// <summary>
    59             /// 青菜
    60             /// </summary>
    61             public string Greens { set; get; }
    62 
    63             public string Meat { set; get; }
    64         }
    65 
    66     }
    View Code
    坚持就是胜利! 如有问题,请反馈微信loverwangshan
  • 相关阅读:
    系统架构技能之设计模式组合模式
    系统架构师基础到企业应用架构单机软件架构
    设计模式系列装饰模式
    设计模式系列命令模式
    设计模式系列外观模式
    设计模式系列原型模式
    设计模式系列代理模式
    设计模式系列桥接模式
    设计模式系列适配器模式
    设计模式系列享元模式
  • 原文地址:https://www.cnblogs.com/loverwangshan/p/10153171.html
Copyright © 2011-2022 走看看