从事件来看委托

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从事件来看委托相关的知识,希望对你有一定的参考价值。

事件是基于委托,为委托提供了一种发布/订阅机制,在dotNet到处都能看到事件,一个简单的例子就是在windows应用程序中,Button类提供了Click事件,这类事件就是委托,触发Click事件时调用的处理程序方法需要定义,其参数也是由委托类型定义的,事件模型可以用下图简要说明。

在这个模型中,事件的响应者通过订阅关系直接关联在事件拥有者的事件上,我们把这种事件模型或者CLR事件模型。因为CLR事件本质上是一个委托实例,我们暂且模仿CLR属性的说法,把CLR事件定义为一个委托类型实例的包装器。

下面一个示例,事件用于连接CarDealer类和Consumer类,CarDealer类提供了一个新车到达时的触发事件,Consumer类订阅该事件,以获得新车到达的通知。从CarDealer类开始,它基于事件提供一个订阅,CarDealer类用event关键字定义了类型为EventHandler<CarInfoEventArgs>的NewCarInfo事件,在NewCar()中,通过调用RaiseNewCarInfo方法触发NewCarInfo事件,这个方法的实现检查委托是否为空,如果不为空,就引发事件。

 public class CarInfoEventArgs : EventArgs
    {
        public string Car { get; private set; }
        public CarInfoEventArgs(string car)
        {
            this.Car = car;
        }
    }

    public class CarDealer
    {
        public event EventHandler<CarInfoEventArgs> NewCarInfo;
        public void NewCar(string car)
        {
            Console.WriteLine("CarDealer,new car {0}", car);
            RaiseNewCarInfo(car);
        }

        protected virtual void RaiseNewCarInfo(string car)
        {
            NewCarInfo?.Invoke(this, new CarInfoEventArgs(car));
        }
    }

Consumer类用作事件侦听器,这个类订阅了CarDealer类的事件,并定义了NewCarIsHere方法,该方法满足EeventHandler<CarInfoEventArgs>委托的要求,其参数类型是object和CarInfoEventArgs.

 public class Consumer
    {
        private string name;
        public Consumer(string name)
        {
            this.name = name;
        }
        public void NewCarIsHere(object sender, CarInfoEventArgs e)
        {
            Console.WriteLine($"{name}:car {e.Car} is new");
        }
    }

需要连接事件发布程序和订阅器,为此使用CarDealer类的NewCarInfo事件,通过“+=”创建一个订阅,然后通过“-=”取消订阅

 var dealer = new CarDealer();
            var myCar = new Consumer("MyCar");
            dealer.NewCarInfo += myCar.NewCarIsHere;
            dealer.NewCar("OneCar");
            dealer.NewCarInfo -= myCar.NewCarIsHere;
            dealer.NewCar("OtherCar");

通过事件,直接连接到发布程序和侦听器,但垃圾回收器有个问题,如果侦听器不在直接引用,发布程序就仍有一个引用,垃圾回收器不能清空侦听器占用的内存,因为发布程序仍有一个引用,会针对侦听器触发事件,这种强连接的模式可以通过弱引用事件模式来解决,即使用WeakEventManager作为发布程序和侦听器之间的中介。

更改Consumer的代码实现IWeakEventListener接口

public class Consumer : IWeakEventListener
    {
        private string name;

        public Consumer(string name)
        {
            this.name = name;
        }

        public void NewCarIsHere(object sender, CarInfoEventArgs e)
        {
            Console.WriteLine("{0}: car {1} is new", name, e.Car);
        }

        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            NewCarIsHere(sender, e as CarInfoEventArgs);
            return true;
        }
    }

更改订阅事件的代码

 var dealer = new CarDealer();
            var myCar = new Consumer("MyCar");
            WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", myCar.NewCarIsHere);

            //dealer.NewCarInfo += myCar.NewCarIsHere;
            dealer.NewCar("OneCar");
            WeakEventManager<CarDealer, CarInfoEventArgs>.RemoveHandler(dealer, "NewCarInfo", myCar.NewCarIsHere);
            //dealer.NewCarInfo -= myCar.NewCarIsHere;
            dealer.NewCar("OtherCar");

WPF使用弱事件模式和事件管理器,在dotNet中委托是类型安全的类,它定义了返回类型和类型参数的类型,委托不仅半酣方法的引用,也可以包含对多个方法引用,lambda表达式与委托直接相关,当参数是委托类型时,就可以直接使用lambda表达式实现委托引用的方法,除了为每个参数和返回类型定义一个新委托之外,还可以使用Action<T>和Func<T>委托,泛型Action<T>委托表示引用一个void返回类型的方法,Func<T>允许调用带返回类型的方法,下面用委托实现经典的冒泡排序,定义一个实体类,在类中定义一个返回bool类型的静态方法,定义一个实现排序方法的BubbleSorter类

Employee[] employees =
            {
            new Employee("Bugs Bunny", 20000),
            new Employee("Elmer Fudd", 10000),
            new Employee("Daffy Duck", 25000),
            new Employee("Wile Coyote", 1000000.38m),
            new Employee("Foghorn Leghorn", 23000),
            new Employee("RoadRunner", 50000)
            };
            BubbleSorter.Sort(employees, Employee.CompareSalary);
            foreach (var employee in employees)
            {
                Console.WriteLine(employee);
            }

 public class BubbleSorter
    {
        static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
        {
            bool swapped = true;
            do
            {
                swapped = false;
                for (int i = 0; i < sortArray.Count - 1; i++)
                {
                    if (comparison(sortArray[i + 1], sortArray[i]))
                    {
                        T temp = sortArray[i];
                        sortArray[i] = sortArray[i + 1];
                        sortArray[i + 1] = temp;
                        swapped = true;
                    }
                }
            } while (swapped);
        }
    }
    public class Employee
    {
        public Employee(string name, decimal salary)
        {
            this.Name = name;
            this.Salary = salary;
        }

        public string Name { get; private set; }
        public decimal Salary { get; private set; }

        public override string ToString()
        {
            return string.Format("{0}, {1:C}", Name, Salary);
        }

        public static bool CompareSalary(Employee e1, Employee e2)
        {
            return e1.Salary < e2.Salary;
        }
    }

实际上,定义一个委托是指定义一个新类,委托实现为派生自基类的System.MulticastDelegate的类,System.MulticastDelegate又派生自基类System.Delegate,C#编译器会识别这个类,并使用其委托语法。

 

以上是关于从事件来看委托的主要内容,如果未能解决你的问题,请参考以下文章

对“xxx”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。 错误解决一例。(代码片段

C#基本功------委托和事件

C# 委托和事件:一个简单的例子

JS事件委托

如何理解事件和委托

javascript事件委托机制详解