胖胖滴加菲猫

导航

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

 

posted on 2018-12-20 23:43  胖胖滴加菲猫  阅读(730)  评论(0编辑  收藏  举报