首页 > 学院 > 开发设计 > 正文

委托和事件 (1)

2019-11-17 02:35:44
字体:
来源:转载
供稿:网友

委托和事件 (1) - 委托简析

个人认为,c#最重要的精髓在于其委托。

说实话现在已经是c#5.0的时代,c#6很快也要出来了,委托作为一个c#1就有的性质,已经早就被更高级的工具例如泛型委托,lambda表达式包装起来了,基本上已经很少有人会在程序中声明一个delegate。不过,了解一下基础也是很好的,

基本概念

委托是一个特殊的类(密封类),可以被视为函数指针,其代表一类和委托签名的输入输出变量类型和个数相同的方法。委托本身可以作为变量传入方法。

借用经典的greetPeople例子,在实际工作中,总会遇到类似的情况,即通过switch来对不同的输入执行不同的结果。但我们看到,其实每个switch执行的方法都很类似,方法的签名还完全相同。此时我们很容易想到的就是当再加入一个新的switch case的时候,我们除了要加一个新方法之外,还要对现成的GreetPeople方法进行修改,这违反了开闭原则(对修改关闭)。有没有一种方法,可以在不修改GreetPeople方法的前提下对程序进行扩展呢?

public class PRogram    {        public static void Main()        {            GreetPeople("Alex", "Chinese");            GreetPeople("Beta", "English");            GreetPeople("Clara", "France");            Console.ReadKey();        }        public static void GreetPeople(string name, string lang)        {            switch (lang)            {                case "English":                    EnglishGreeting(name);                    break;                case "Chinese":                    ChineseGreeting(name);                    break;                case "France":                    FrenchGreeting(name);                    break;            }        }        public static void EnglishGreeting(string name)        {            Console.WriteLine("Morning, " + name);        }        public static void ChineseGreeting(string name)        {            Console.WriteLine("早上好, " + name);        }        public static void FrenchGreeting(string name)        {            Console.WriteLine("Bonjour, " + name);        }    }

首先,我们要放弃使用switch,否则我们终究避免不了修改GreetPeople方法的命运。之后,我们自然而然的会想,假设我们在主函数里面传入的第二变量不是字符串,而是方法名,那么似乎我们就不需要那个switch了。因为我们会直接去到对应的方法,不用switch再分派过去。那么这件事该怎么实现呢?传入方法名到底意味着什么呢?这些方法的签名全都一样,我是否可以用某种手法将他们封装起来呢

于是,委托就出现了,它可以解决上面我们所有的问题。委托代表了一类具有相同签名的方法,可以变身为其中任何一个。委托也可以作为变量传入方法,其行为和其他类型例如int,string完全一样。很多人觉得委托很不好理解,是因为委托代表的是方法,而普通类型代表的都是值或者对象。比如string,其可以代表任何的字符串,int也是可以代表在某个取值范围中任何的整数一样。委托则代表着某一类方法(视其定义而定),当某个函数的其中一个变量是委托时,意味着我们将要传入一个可以被该委托所代表的方法名。委托是方法的指针,可以指向不同的方法,类比一下,如同string可以指向堆上的字符串,int可以指向栈上的整数一样。

public class Program    {        //现在这个委托代表了一类输入一个字符串,没有输出的方法        public delegate void GreetPeopleDelegate(string name);        public static void Main()        {            //利用委托,传入不同的方法会得到不同的结果            GreetPeople("Alex", ChineseGreeting);            GreetPeople("Beta", EnglishGreeting);            GreetPeople("Clara", FrenchGreeting);            Console.ReadKey();        }        //委托可以作为方法的变量,从而代替switch        public static void GreetPeople(string name, GreetPeopleDelegate aGreetPeopleDelegate)        {            aGreetPeopleDelegate(name);        }        public static void EnglishGreeting(string name)        {            Console.WriteLine("Morning, " + name);        }        public static void ChineseGreeting(string name)        {            Console.WriteLine("早上好, " + name);        }        public static void FrenchGreeting(string name)        {            Console.WriteLine("Bonjour, " + name);        }    }

委托的方法和属性

1. MulticastDelegate(委托自己所在的密封类)

小写的delegate是你用来声明委托的关键字,当你声明完之后,编译器创建一个新的密封类,该类的类型是MulticastDelegate(继承自System.MultipleDelegate,其再继承自System.Delegate)这就是大写的和小写d的delegate关键字的区别。

这个新的密封类定义了三个方法,invoke, begininvoke和endinvoke。invoke是当你调用委托所代表的方法时隐式执行的,例如aGreetPeopleDelegate(name)实际上和aGreetPeopleDelegate.Invoke(name)没有区别。所以Invoke的方法签名永远和委托本身相同,即如果某委托签名为int a(int x, int y)则它的invoke签名一定是public int Invoke(int x, int y)。

后两者则赋予委托异步的能力。这两个方法放到多线程系列中进行分析。

2.System.MultipleDelegate和委托的调用列表(方法链)

System.MultipleDelegate中重要的方法GetInvocationList()获得当前委托所代表的方法的各种信息。注意这个方法返回的是一个数组,这也就是说,委托可以同时代表多个方法(此时,invoke委托会将该组方法顺序一个一个执行),这也叫做委托的多路广播。通过+=和-=,我们可以为委托增加和减少方法。我们无需深入研究方法链是如何实现的,但以下几个事情需要知道:

1. 可以重复增加相同的方法,此时该方法将执行两次

2. 可以删除委托所有的方法,即委托可以暂时不代表方法,此时invoke委托将什么都不发生

3. 即使不小心多删除了方法一次,也不会出现异常(如增加了一个方法然后误删除了两次),此时委托暂时不代表任何方法

4. +=和-=是操作符的重载,本质是调用System.Delegate中的Combine和Remove方法

System.MultipleDelegate还重载了==和!=,判断两个委托是否相等仅仅看它们代表的方法链是否相等(即都是指向相同对象上的相同方法)。

3.System.Delegate

System.Delegate中有两个重要的公共成员target和method。其中method代表方法的信息,而如果Method代表一个静态成员,则Target为null,否则,target代表方法所在的对象。通过GetInvocationList()我们可以查看当前委托中方法链的信息。另外这个类还有Combine和Remove方法,其已经被子类重载故不需要直接调用他们。

public class Program    {        public delegate void GreetPeopleDelegate(string name);        public static void Main()        {            //实例化委托一定要为其指派一个符合要求的方法            GreetPeopleDelegate aGreetPeopleDelegate = new GreetPeopleDelegate(ChineseGreeting);            PrintInvocationList(aGreetPeopleDelegate.GetInvocationList());            //增加一个方法            aGreetPeopleDelegate += EnglishGreeting;            PrintInvocationList(aGreetPeopleDelegate.GetInvocationList());            anotherClass a = new anotherClass();            //增加一个非静态方法            aGreetPeopleDelegate += a.NonStaticGreeting;            PrintInvocationList(aGreetPeopleDelegate.GetInvocationList());            Console.ReadKey();        }        //观看当前委托中代表的方法链        public static void PrintInvocationList(Delegate[] aList)        {            foreach (var delegateMethod in aList)            {                //Method代表当前维护的方法的详细信息                //如果Method代表一个静态成员,则Target为null,否则,target代表方法所在的对象                Console.WriteLine(string.Format("Method name: {0}, value: {1}", delegateMethod.Method, delegateMethod.Target));            }            Console.WriteLine("------------------------------------");        }        public static void EnglishGreeting(string name)        {            Console.WriteLine("Morning, " + name);        }        public static void ChineseGreeting(string name)        {            Console.WriteLine("早上好, " + name);        }        public static void FrenchGreeting(string name)        {            Console.WriteLine("Bonjour, " + name);        }    }    public class anotherClass    {        public void NonStaticGreeting(string name)        {            Console.WriteLine("Bonjour, " + name);        }    }

动态维护委托的调用列表

上面说了委托都是有一个调用列表的,我们可以动态的操作他,为他添加或者删除成员。如果我们创建一个公共的委托成员列表,则可以很容易的实现多路广播。下面例子来自精通c#第六版。其中调用列表

public CarEngineHandler methodList;

是公共的,并且外部方法main会创建一个新的实例作为订阅者,在适当情形下,调用委托然后执行委托列表中的方法。

public class Program    {        public static void Main()        {            //创建了一个新的订阅者            var c = new Car("Mycar", 0, 100);            //该订阅者(消费者)订阅了方法OnCarEvent1            c.methodList += OnCarEvent1;            //取消注释实现多路广播,此时将会执行两个方法            //c.methodList += OnCarEvent2;            for (int i = 0; i < 10; i++)            {                c.Accel(20);            }            Console.ReadKey();        }        public static void OnCarEvent1(string msg)        {            Console.WriteLine("***** message from car *****");            Console.WriteLine("=> " + msg);            Console.WriteLine("****************************");        }        public static void OnCarEvent2(string msg)        {            Console.WriteLine("=> " + msg.ToUpper());        }    }    public class Car    {        public string name { get; set; }        public int currentSpeed { get; set; }        public int MaxSpeed { get; set; }        private bool isDead { get; set; }        public delegate void CarEngineHandler(string message);        public CarEngineHandler methodList;        public Car(string name, int currentSpeed, int MaxSpeed)        {            this.name = name;            this.currentSpeed = currentSpeed;            this.MaxSpeed = MaxSpeed;            this.isDead = false;        }        public void Accel(int delta)        {            //死亡时执行订阅列表中的方法            if (isDead)            {                if (methodList != null)                    methodList("Sorry, car is broken");            }            else            {                currentSpeed += delta;                if (currentSpeed >= MaxSpeed) isDead = true;                else Console.WriteLine("Current speed: " + currentSpeed);            }        }
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表