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

    委托是一个密封类,继承自System.MulticastDelegate,里面内置了invoke等几个方法,简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同时也是粒度更小的“接口”(约束了指向方法的签名)。可以认为委托是持有一个或多个方法的对象。当然,正常情况下你不会想要“执行”一个对象,但委托与典型的对象不同。可以执行委托,这时委托会执行它所“持有”的方法。
    委托是面向对象的、类型安全的,是引用类型。

    委托的声明、实例化和调用

    委托是引用类型,因此有引用和对象。在委托类型声明之后,我们可以声明变量并创建类型的对象。

    public delegate void NoReturnNoPara();//1 声明委托
    //把方法包装成对象,invoke的时候自动执行方法
    NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);//2 委托的实例化
    method.Invoke();//3 委托实例的调用
    method();
    

    委托解耦

    委托把方法包装成对象,invoke的时候自动执行方法。

    如果我们需要通过判断条件来进行筛选,可以通过委托包装方法。
    如下,我们在studentList中通过多个判断条件将满足条件的放入容器

    {
        //找出年龄大于25
        List<Student> result = new List<Student>();//准备容器
        foreach (Student student in studentList)//遍历数据源
        {
            if (student.Age > 25)//判断条件
            {
                result.Add(student);//满足条件的放入容器
            }
        }
        Console.WriteLine($"结果一共有{result.Count()}个");
    }
    

    可以将判断逻辑传递进来+实现共用逻辑委托解耦,减少重复代码

    public class ListExtend
    {
        public delegate bool ThanDelegate(Student student);
        private bool Than(Student student)
        {
            return student.Age > 25;
        }
    
        public void Show()
        {
            ThanDelegate method = new ThanDelegate(this.Than);
            List<Student> result = this.GetListDelegate(studentList, method);
            Console.WriteLine($"结果一共有{result.Count()}个");
        }
    
        private List<Student> GetListDelegate(List<Student> source, ThanDelegate method)
        {
            List<Student> result = new List<Student>();
            foreach (Student student in source)
            {
                if (method.Invoke(student))
                {
                    result.Add(student);
                }
            }
            return result;
        }
    }
    

    另外,委托可以做通用的异常处理,其中Action对应任何逻辑。

    /// <summary>
    /// 通用的异常处理
    /// </summary>
    /// <param name="act">对应任何的逻辑</param>
    public static void SafeInvoke(Action act)
    {
        try
        {
            act.Invoke();
        }
        catch (Exception ex)//按异常类型区分处理
        {
            Console.WriteLine(ex.Message);
        }
    }
    

    异步多线程

    委托也可以与多线程结合,异步调用。

    WithReturnNoPara method = new WithReturnNoPara(this.GetSomething);
    int iResult = method.Invoke();
    iResult = method();
    var result = method.BeginInvoke(null, null);//异步调用
    method.EndInvoke(result);
    

    多播委托

    链式委托也被称为“多播委托”,其本质是 一个由多个委托组成的链表 。我们知道,所有的自定义委托都继承自System.MulticastDelegate类,这个类就是为链式委托而设计的。当两个及以上的委托被链接到一个委托链时,调用头部的委托将导致该链上的所有委托方法都被执行。
    像上面实例化委托的时候,一个委托类型的变量只能保存一个方法,使用多播委托,一个委托类型的变量可以保存多个方法,多播委托可以增加、减少委托,Invoke的时候可以按顺序执行。
    += 为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行,如果多播委托带返回值,结果则以最后的为准。

    当然,在使用+=运算符时,实际发生的是创建了一个新的委托,其调用列表是委托加上方法的组合。然后创建并更新新的委托。另外,+= 和 -= 对null是不会报错的;且 -= 只针对委托中具有同一个实例,无法移除不同实例的方法。注意:多播委托不能异步调用(即调用BeginInvoke()),因为多播委托里面有很多方法,异步调用的时候不知道该怎样执行

    Student studentNew = new Student();
    
    NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);
    method += new NoReturnNoPara(this.DoNothing);
    method += new NoReturnNoPara(DoNothingStatic);
    method += new NoReturnNoPara(Student.StudyAdvanced);
    method += new NoReturnNoPara(new Student().Study);
    method += new NoReturnNoPara(studentNew.Study);
    method.Invoke();
    
    //method.BeginInvoke(null, null);//error,多播委托是不能异步的
    foreach (NoReturnNoPara item in method.GetInvocationList())
    {
        item.BeginInvoke(null, null);
    }
    //-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常
    method -= new NoReturnNoPara(this.DoNothing);
    method -= new NoReturnNoPara(DoNothingStatic);
    method -= new NoReturnNoPara(Student.StudyAdvanced);
    method -= new NoReturnNoPara(new Student().Study);//不是同一个实例,所以是不同的方法
    method -= new NoReturnNoPara(studentNew.Study);
    method.Invoke();
    

    有一种通用的应用场景。当我们需要设定一个发布者, 导致一系列的触发动作,并且动作可能会发生变化和扩展,则可以通过委托共用相同的触发逻辑。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MyDelegateEvent.Event
    {
        /// <summary>
        /// 发布者
        /// </summary>
        public delegate void MiaoDelegate();
    
        public class Cat
        {
            public void Miao()
            {
                Console.WriteLine("{0} Miao", this.GetType().Name);
                new Mouse().Run();
                new Baby().Cry();
                new Mother().Wispher();
            }
    
            public MiaoDelegate MiaoDelegateHandler;
            public void MiaoNew()
            {
                Console.WriteLine("{0} MiaoNew", this.GetType().Name);
                if (this.MiaoDelegateHandler != null)
                {
                    this.MiaoDelegateHandler.Invoke();
                }
            }
          }
    }
    

    之后便可以通过委托,为Cat添加动作

    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);
    

    同理,也可以通过事件

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MyDelegateEvent.Event
    {
        public class Cat
        {
            public event MiaoDelegate MiaoDelegateHandlerEvent;
            public void MiaoNewEvent()
            {
                Console.WriteLine("{0} MiaoNewEvent", this.GetType().Name);
                if (this.MiaoDelegateHandlerEvent != null)
                {
                    this.MiaoDelegateHandlerEvent.Invoke();
                }
            }
        }
    }
    

    事件

    当我们使用委托场景时,我们很希望有这样两个角色出现:广播者和订阅者。我们需要这两个角色来实现订阅和广播这种很常见的场景。
    广播者这个角色应该有这样的功能:包括一个委托字段,通过调用委托来发出广播。而订阅者应该有这样的功能:可以通过调用 += 和 -= 来决定何时开始或停止订阅。事件就是描述这种场景模式的一个词。事件是委托的一个子集,为了满足“广播/订阅”模式的需求而生。
    声明一个事件很简单,只需在声明一个委托对象时加上event关键字就行。

    public event MiaoDelegate MiaoDelegateHandlerEvent;
    

    事件是委托类型的一个实例,并且做出了安全限制,可以避免委托的实例直接invoke或者对委托直接赋值,从而限制变量被外部调用和直接赋值。在一种特殊的例子中,子类也不能直接调用父类的事件。
    因此,事件:可以把一堆可变的动作/行为封装出去,交给第三方来指定。和预定义一样,程序设计的时候,我们可以把程序分成两部分一部分是固定的,直接写死;还有不固定的地方,通过一个事件去开放接口,外部可以随意扩展动作。
    但事件有一系列规则和约束用以保证程序的安全可控,事件只有 += 和 -= 操作,这样订阅者只能有订阅或取消订阅操作,没有权限执行其它操作。如果是委托,那么订阅者就可以使用 = 来对委托对象重新赋值(其它订阅者全部被取消订阅),甚至将其设置为null,甚至订阅者还可以直接调用委托,这些都是很危险的操作,广播者就失去了独享控制权。
    事件保证了程序的安全性和健壮性

    事件的标准模式

    .NET 框架为事件编程定义了一个标准模式。设定这个标准是为了让.NET框架和用户代码保持一致。System.EventArgs是标准模式的核心,它是一个没有任何成员,用于传递事件参数的基类。

    using System;
    
    
    public class PriceChangedEventArgs : System.EventArgs
    {
        public readonly decimal OldPrice;
        public readonly decimal NewPrice;
        public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) { OldPrice = oldPrice; NewPrice = newPrice; }
    }
    public class IPhone6
    {
        decimal price;
        public event EventHandler<PriceChangedEventArgs> PriceChanged;
        protected virtual void OnPriceChanged(PriceChangedEventArgs e)
        {
            if (PriceChanged != null) PriceChanged(this, e);
        }
        public decimal Price
        {
            get { return price; }
            set
            {
                if (price == value)
                    return;
                decimal oldPrice = price;
                price = value;             // 如果调用列表不为空,则触发。
                if (PriceChanged != null)
                    OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
            }
        }
    }
    class Program
    {
        static void Main()
        {
            IPhone6 iphone6 = new IPhone6() { Price = 5288M };
            // 订阅事件
            iphone6.PriceChanged += iphone6_PriceChanged;          // 调整价格(事件发生)
            iphone6.Price = 3999;
            Console.ReadKey();
        }
        static void iphone6_PriceChanged(object sender, PriceChangedEventArgs e)
        {
            Console.WriteLine("年终大促销,iPhone 6 只卖 " + e.NewPrice + " 元, 原价 " + e.OldPrice + " 元,快来抢!");
        }
    }
    

    静态方法和实例方法对于委托的区别

    当一个类的实例的方法被赋给一个委托对象时,在上下文中不仅要维护这个方法,还要维护这个方法所在的实例。System.Delegate 类的Target属性指向的就是这个实例。对于静态方法,System.Delegate 类的Target属性是Null,所以将静态方法赋值给委托时性能更优。

    参考:
    委托和事件
    【C#系列】你应该知道的委托和事件

  • 相关阅读:
    Go
    Go
    Go
    Go
    Go
    Go
    爬虫常用相关库
    Go
    python基础第7天(day19)
    python基础第五天(day17)元组,集合,字符串操作 字符编码:
  • 原文地址:https://www.cnblogs.com/hellojamest/p/14232000.html
Copyright © 2011-2022 走看看