C#委托和事件

泛型委托

上一章我们介绍了什么是委托、委托的基本使用一级委托的意义。但在实际使用过程中我们很少自定义委托,以为官方框架为我们提供了两个内置的委托ActionFunc通过这个l内置的委托基本上可以实现所有的需求,而不需要再自定义委托,而且官方提供这种封装,自然就是希望大家都统一使用

Action/Func基本介绍

Action/Func。下面我们详细来介绍一下这两个内置委托委托:
Action/Func是从.NetFramework3.0就出现的。
Action是系统提供了0到16个泛型参数不带返回值的委托。基本定义:

public delegate void Action<in T>(T obj);
public delegate void Action<in T1,in T2>(T1 arg1,T2 arg2);
public delegate void Action<in T1,in T2,in T3>(T1 arg1, T2 arg2, T3 arg3);
...

Func是系统提供0到16个泛型参数带泛型返回值的委托。基本定义:

public delegate TResult Func<out TResult>();
public delegate TResult Func<in T, out TResult>(T arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
...

定义中in/out关键字就是泛型里的逆变与协变。如果不记得这个可以去复习一前面写泛型介绍。或者查看查看这边文章:协变/逆变,看完这篇就懂了

Action/Func的使用

Action action = new Action(this.DoNothing);
Action action0 = this.DoNothing;//是个语法糖,就是编译器帮我们添加上new Action
Action<int> action1 = this.ShowInt;
 private void ShowInt(int i)
        { }
 Func<int> func0 = this.Get;
 int iResult = func0.Invoke();
Func<int, string> func1 = this.ToString;
public int Get()
        {
            return 1;
        }

对比一下上一章自定义的委托NoReturnNoPara

 Action action0 = this.DoNothing;
 NoReturnNoPara method = this.DoNothing;
   private void DoAction(Action act)
        {
            act.Invoke();
        }
 this.DoAction(action0);
 this.DoAction(method);//这个会报错
 

委托的本质是类,Action和NoReturnNoPara是不同的类,虽然实例化都可以传递相同的方法,但是没有父子关系,所以是不能替换的,就像Student和Teacher两个类,实例化都是传递id/name,但是二者不能替换的。

Action/Func的意义

想象一下起始委托的定义无非就是方法参数与返回值,没有像Action/Func这样统一委托,明明是同样的参数定义和返回值(委托签名一致)但是你定义的委托不一样就是一个独立的类导致参数不一致,而Action/Func的出现统一了委托参数。框架以前就出现了这个问题,出现过N多个委托,委托签名一致的,实例化完的对象却不能通用:

new Thread(new ParameterizedThreadStart()).Start();

ParameterizedThreadStart委托也是内置的。

ThreadPool.QueueUserWorkItem(new WaitCallback())

WaitCallback委托也是内置的。
现在我们使用Action:

Task.Run(new Action<object>());

Action/Func 框架预定义的,新的API一律基于这些委托来封装。因为.Net向前兼容,以前的版本去不掉了,保留着,这是历史包袱。所以以后开发都不要定义新的委托。

委托的多种途径实例化

实例化唯一限制就是方法的参数列表和返回值类型必须和委托约束的一致。

 Action method = this.DoNothing;
 Action method = DoNothingStatic;//内部静态方法也可以
Action method = new Student().Study;//实例化一个类调用类里的的方法也可以
 Action method = Student.StudyAdvanced;//外部静态方法

多播委托

在上一章我们查看委托定义的时候,委托都是继承自MulticastDelegate,我们直接翻译这几个单词就知道,就是交多播委托。委托一开始就是为了能多播的。

Action method = this.DoNothing;
                method += this.DoNothing;
                method += DoNothingStatic;
                method += new Student().Study;
                method += Student.StudyAdvanced;
    method -= this.DoNothing;
                method -= DoNothingStatic;
                method -= new Student().Study;//去不掉  原因是不同的实例的相同方法,并不吻合
                method -= Student.StudyAdvanced;
                method.Invoke();             

多播委托有啥用呢?

  1. 一个委托实例包含多个方法,可以通过+=/-=去增加/移除方法,Invoke时可以按顺序执行全部动作
  2. 任何一个委托都是多播委托类型的子类,可以通过+=去添加方法
  3. += 给委托的实例添加方法,会形成方法链,Invoke时,会按顺序执行系列方法
  4. -= 给委托的实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除,且只移除一个,如果没有匹配,就啥事儿不发生
  5. 中间出现未捕获的异常,直接方法链结束了
Action method = this.DoNothing;
                method += this.DoNothing;
                method += DoNothingStatic;
                method += new Student().Study;
                method += Student.StudyAdvanced;
                method.BeginInvoke(null, null);//启动线程来完成计算会报错,多播委托实例不能异步
                //如果硬是要异步通过下面的方式实现:
                foreach (Action item in method.GetInvocationList())
                {
                    item.Invoke();
                    item.BeginInvoke(null, null);
                }

如果多播委托返回值呢,返回值回事怎么样的呢:

 Func<int> func = this.Get;
                func += this.Get2;
                func += this.Get3;
                int iResult = func.Invoke();
                //结果是3  以最后一个为准,前面的丢失了。。所以一般多播委托用的是不带返回值的

多播委托的作用

我先设定一个场景比如深夜,一只猫叫了一声会引发系列事情,我们通过代码来展现入股:

  public class Cat
    {
        public void Miao()
        {
            Console.WriteLine("{0} Miao", this.GetType().Name);
            new Dog().Wang();//狗叫
            new Mouse().Run();//老鼠跑
            new Baby().Cry();//宝宝哭
            new Mother().Wispher();//妈妈安抚
            new Brother().Turn();//兄弟翻一个身
            new Father().Roar();//爸爸咆哮
            new Neighbor().Awake();//邻居醒来
            new Stealer().Hide();//小偷赶紧藏起来
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
        }
 Cat cat = new Cat();
 cat.Miao();

猫叫一声会引发以上一系列事情,触发一系列动作,但是这么实现依赖太重,依赖了多个类型,任何类型的变化都得修改猫,职责耦合,猫不仅自己Miao,还得找各种对象执行各种动作甚至控制顺序,任意环节的增加或减少调整顺序都得修改猫。
而我们需要的是猫就是猫,只做自己的事情。需求是猫Miao一声—触发一系列动作,动作从哪里来?不管,我只负责调用,这就用到了多播委托,我们把代码升级一下:

 public class Cat
    {
        public void Miao()
        {
            Console.WriteLine("{0} Miao", this.GetType().Name);
            new Dog().Wang();
            new Mouse().Run();
            new Baby().Cry();
            new Mother().Wispher();
            new Brother().Turn();
            new Father().Roar();
            new Neighbor().Awake();
            new Stealer().Hide();
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
        }
        
  
        public Action CatMiaoAction;
        public void MiaoDelegate()
        {
            Console.WriteLine("{0} MiaoDelegate", this.GetType().Name);
            this.CatMiaoAction?.Invoke();
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
        }      
}

调用:

 Cat cat = new Cat();
 cat.CatMiaoAction += new Dog().Wang;
                    cat.CatMiaoAction += new Mouse().Run;
                    cat.CatMiaoAction += new Baby().Cry;
                    cat.CatMiaoAction += new Mother().Wispher;

                    cat.CatMiaoAction.Invoke();
                    cat.CatMiaoAction = null;

                    cat.CatMiaoAction += new Brother().Turn;
                    cat.CatMiaoAction += new Father().Roar;
                    cat.CatMiaoAction += new Neighbor().Awake;
                    cat.CatMiaoAction += new Stealer().Hide;
                    cat.MiaoDelegate();

去除依赖,Cat稳定了;还可以有多个Cat实例。

事件

事件是什么,事件event:一个委托的实例,带一个event关键字。

委托与事件的区别和联系

我们来接着上面的示例来看看委托和事件有什么区别:

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

        public Action CatMiaoAction;
        public void MiaoDelegate()
        {
            Console.WriteLine("{0} MiaoDelegate", this.GetType().Name);
            this.CatMiaoAction?.Invoke();
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
        }
        public void MiaoEvent()
        {
            Console.WriteLine("{0} MiaoEvent", this.GetType().Name);
            this.CatMiaoActionHandler?.Invoke();
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
        }
    }

调用:

                    cat.CatMiaoActionHandler += new Dog().Wang;
                    cat.CatMiaoActionHandler += new Mouse().Run;
                    cat.CatMiaoActionHandler += new Baby().Cry;

                    cat.CatMiaoActionHandler.Invoke();//会报错
                    cat.CatMiaoActionHandler = null;//会报错

事件和委托最大的一个区别就是事件有限制权限,只允许在事件声明类里面去invoke和赋值,不允许外面,甚至子类。

 public class MiniCat : Cat
    {
        public void Do()
        {
            this.CatMiaoActionHandler = null;//即使是子类  也不行,也会报错
        }
    }

所以只能通过调用内部方法触发事件,事件能只能从内部触发:

                    cat.CatMiaoActionHandler += new Mother().Wispher;
                    cat.CatMiaoActionHandler += new Brother().Turn;
                    cat.CatMiaoActionHandler += new Father().Roar;
                    cat.CatMiaoActionHandler += new Neighbor().Awake;
                    cat.CatMiaoActionHandler += new Stealer().Hide;
                    cat.MiaoEvent();

通过上面的示例我们可以知道委托是一种类型,事件是委托类型的一个实例,加上了event的权限控制就如Student是一种类型,Tony就是Student类型的一个实例。

winform里的事件

winform无处不在—WPF—webform服务端控件/请求级事件
为啥要用事件?事件究竟能干什么?事件(观察者模式)能把固定动作和可变动作分开,完成固定动作,把可变动作分离出去,由外部控制,搭建框架时,恰好就需要这个特点,可以通过事件去分离可变动作,支持扩展。
在winform中我点击一个button会触发click事件,实际这些事件都是我们绑定的:

  this.btnTest.Click += new System.EventHandler(this.btnTest_Click);
  /// <summary>
        /// 控件的点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnTest_Click(object sender, EventArgs e)
        {
            Console.WriteLine("************************");
            Console.WriteLine("用户登录");
            Console.WriteLine("************************");
        }

我们大概设想一下这里面的过程:
初始化:启动Form—初始化控件Button—Click事件—+=一个动作
触发:点击按钮–鼠标操作–操作系统收到信号–发送给程序–程序得接受信号,判断控件–登陆–
事件只能类的内部发生)Button类自己调用Click–肯定是触发了Click事件—登陆动作就会执行
点击按钮–鼠标操作–操作系统收到信号–发送给程序–程序得接受信号,判断控件–支付–(事件只能类的内部发生)Button类自己调用Click–肯定是触发了Click事件—支付动作就会执行
2次按钮操作,大部分东西都是一样的,就是具体业务不一样的,封装的控件就完成了固定动作–接受信号&默认动作。可变部分,就是事件—是一个开放的扩展接口,想扩展什么就添加什么。是用event限制了权限 避免外部乱来。

自定义事件实现流程演示

在事件中我们是分为两个角色一个发布者,发布事件的主体,一个订户,关注时间,时间发生后,自己做出对应的动作。两者通过订阅关联起来而订阅就相当于我们代码中+=
我们设想一个场景示例,比如有一个腾讯在线课程,这个课程就是发布者,而它的订户就是学生和腾讯,学生关注课程的价格变化,腾讯作为管理者也对课程的价格变化关注。
定义一个EventStandard

 public class EventStandard
    {
    }

EventStandard中定义:

  /// <summary>
        /// 事件的发布者,发布事件并且在满足条件的时候,触发事件
        /// </summary>
        public class Lesson
        {
            public int Id { get; set; }
            public string Name { get; set; }

            private int _price;
            public int Price
            {
                get
                {
                    return this._price;
                }
                set
                {
                    if (value > this._price)
                    {
                        this.IncreaseHandler?.Invoke(this,
                            new XEventArgs()
                            {
                                OldPrice = this._price,
                                NewPrice = value
                            });
                        this._price = value;
                    }
                }
            }

            /// <summary>
            /// 打折事件
            /// 
            /// </summary>
            public event EventHandler IncreaseHandler;

        }

EventStandard中定义:


        /// <summary>
        /// 订户:关注事件,事件发生后,自己做出对应的动作
        /// </summary>
        public class Student
        {
            public void Buy(object sender, EventArgs e)
            {
                Lesson lesson = (Lesson)sender;
                Console.WriteLine($"This is {lesson.Name} Lesson");

                XEventArgs args = (XEventArgs)e;
                Console.WriteLine($"之前价格{args.OldPrice}");
                Console.WriteLine($"现在价格{args.NewPrice}");
                Console.WriteLine("果断买了!!!");
            }
        }
        public class Tencent
        {
            public void Popularize(object sender, EventArgs e)
            {
                Lesson lesson = (Lesson)sender;
                Console.WriteLine($"This is {lesson.Name} Lesson");

                XEventArgs args = (XEventArgs)e;
                Console.WriteLine($"之前价格{args.OldPrice}");
                Console.WriteLine($"现在价格{args.NewPrice}");
                Console.WriteLine("广大用户请留意!!!");
            }
        }

EventStandard中定义:

  /// <summary>
        /// 事件参数  一般会为特定的事件去封装个参数类型
        /// </summary>
        public class XEventArgs : EventArgs
        {
            public int OldPrice { get; set; }
            public int NewPrice { get; set; }
        }

EventStandard中定义:

///定义调用方法
  public static void Show()
        {
            Lesson lesson = new Lesson()
            {
                Id = 123,
                Name = "零基础到多项目实战班",
                Price = 2699
            };
            //订阅:把订户和发布者的事件关联起来
            lesson.IncreaseHandler += new Student().Buy;
            lesson.IncreaseHandler += new Tencent().Popularize;
            lesson.Price = 3999;
        }

调用:

EventStandard.Show();

这就是一个标准的事件实现流程。

观察者模式(额外)

在上面介绍多播委托的示例中我们使用委托和事件的方式实现,其实还可以通过观察者模式实现:

 public class Cat
    {
        public void Miao()
        {
            Console.WriteLine("{0} Miao", this.GetType().Name);
            new Dog().Wang();
            new Mouse().Run();
            new Baby().Cry();
            new Mother().Wispher();
            new Brother().Turn();
            new Father().Roar();
            new Neighbor().Awake();
            new Stealer().Hide();
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
        }
        private List<IObject> _ObserverList = new List<IObject>();
        public void AddObserver(IObject observer)
        {
            this._ObserverList.Add(observer);
        }
        public void MiaoObserver()
        {
            Console.WriteLine("{0} MiaoObserver", this.GetType().Name);
            foreach (var item in this._ObserverList)
            {
                item.DoAction();
            }
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
            Console.WriteLine("*&^&*^*^*(^&*^&*^&*^&*^");
        }
    }
 public interface IObject
    {
        void DoAction();
    }

调用:

                    cat.AddObserver(new Dog());
                    cat.AddObserver(new Mouse());
                    cat.AddObserver(new Baby());
                    cat.AddObserver(new Mother());
                    cat.AddObserver(new Brother());
                    cat.AddObserver(new Father());
                    cat.AddObserver(new Neighbor());
                    cat.AddObserver(new Stealer());
                    cat.MiaoObserver();

通过接口定义一个统一方法,把需要执行动作全部通过内部的一个集合存储,再遍历执行,因为有接口统一定义一个方法,所不存在方法不存在的问题。其所有可变动作都是通过外部控制。
观察者模式能把固定动作和可变动作分开,完成固定动作,把可变动作分离出去,由外部控制。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值