委托和事件 (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);
            }
        }
    }

从委托到事件

上个例子中的委托有一个问题,就是其不够安全。调用者可以直接访问委托对象CarEngineHandler,并且还能对其调用列表:

1 invoke,即可以随时使用委托

2 +=或者-=,甚至直接赋值(=)也可以

有时候,我们并不希望用户可以更改委托的成员。而且,我们希望委托不能被用户Invoke,而是在特定的时候被委托的订阅者调用。也就是说我们希望下面两句代码都不通过编译:

//为委托赋以一个全新的对象(我们不希望其他代码可以改变委托指向)
c.methodList = OnCarEvent1;

//直接调用委托(我们不希望其他代码可以直接调用,除非经过许可)
c.methodList.Invoke("test");

此时,一个自然的想法就是将委托本身定义为private,但如果这样做,外部的所有类都无法使用该委托。所以我们还要搞若干公共的方法,作为外部类使用内部私有委托的桥梁。下面代码中,methodList是私有的所以我们不能直接对他操作,我们要通过Car类的两个公共方法操作他。(无关的代码已省略)

public class Program
    {
        public static void Main()
        {
            //创建了一个新的订阅者
            var c = new Car("Mycar", 0, 100);
            c.Addmethod(OnCarEvent1);
            c.Invoke("test");
    }

    public class Car
    {public delegate void CarEngineHandler(string message);

        private CarEngineHandler methodList;

        public CarEngineHandler Addmethod(CarEngineHandler aMethod)
        {
            methodList += aMethod;
            return methodList;
        }

        public void Invoke(string msg)
        {
            methodList.Invoke(msg);
        }
    }

但问题就来了,那对于所有的委托,如果我们要追求安全,岂不是都要弄这些方法,而且方法还比较多,有添加方法,删除方法,方法的同步和异步的调用等。这看上去非常麻烦,要打很多的代码。相信这时候你也想到了,又有一个强大的东西要出场了,它可以解决上面所有的问题,它就是事件。

时间: 2024-11-04 06:52:28

委托和事件 (1) - 委托简析的相关文章

委托和事件匿名委托

public delegate void maojiaodelegate (object Sender, EventArgs e); /// <summary> /// 触发对象 观察者 人醒来 /// </summary> public class BigMaster { private Mao _cat; public BigMaster(Mao cat) { _cat = cat; _cat.onjiao += new maojiaodelegate(onxinglaile)

13委托和事件

1.委托是什么? 委托是一种数据类型,像类一样(可以声明委托类型变量).方法参数可以是 int.string.类类型 void M1(int n){ }        √ void M2(string s){ }    √ void M3(Person p){ }    √ 那么能不能把方法也作为参数进行传递? 委托就是一种数据类型,用来存放方法的了数据类型 委托的声明方式:delegate 返回值类型 委托的名(参数) 存储什么样的方法就声明什么类型(方法参数与返回值)的委托. 声明的委托是一

C# 事件和委托

1 public class Heater { 2 private int temperature; 3 public string type = "RealFire 001"; // 添加型号作为演示 4 public string area = "China Xian"; // 添加产地作为演示 5 //声明委托 6 public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e)

C#委托与事件学习笔记

今天跟随视频学习了一下C#中最重要的一些概念之委托与事件.老杨的视频讲的还是挺深入浅出,不过刚接触C#.NET的人还是朦朦胧胧,就像张子阳先生说的"每次见到委托和事件就觉得心里别(biè)得慌,混身不自在".跨过这道坎的人就有种一览众山小的感觉了.我又浏览了皱华栋老师JamesZou的博文<深入理解C#委托及原理>(地址:http://www.cnblogs.com/jameszou/archive/2011/07/21/2112497.html),以及张子阳Jimmy Z

9、委托、事件、Lambda

关于委托,肯定是要有问题的. 第一个问题,委托用来干什么? 看.net中的表述:在.net平台下,委托类型用来定义和相应应用程序中的回调.(回调?处理内存中两个实体双向通信的一种技术.) 第二个问题,委托和C++(其实起源于C语言)函数指针? 必然要说区别,虽然委托和函数指针都是指向以后要调用的方法.但委托相比于函数指针来说,主要有两点优势:一,委托是对象(我们知道,委托和类在一个层次).二,委托是内置支持多路广播和异步方法调用的. 关于事件. 问题,事件用来干什么? 事件是为委托服务的,它使得

《C#图解教程》读书笔记之五:委托和事件

本篇已收录至<C#图解教程>读书笔记目录贴,点击访问该目录可获取更多内容. 一.委托初窥:一个拥有方法的对象 (1)本质:持有一个或多个方法的对象:委托和典型的对象不同,执行委托实际上是执行它所"持有"的方法.如果从C++的角度来理解委托,可以将其理解为一个类型安全的.面向对象的函数指针. (2)如何使用委托? ①声明委托类型(delegate关键字) ②使用该委托类型声明一个委托变量 ③为委托类型增加方法 ④调用委托执行方法 (3)委托的恒定性: 组合委托.为委托+=增加

C#高级知识点概要(1) - 委托和事件

作者:linybo 要成为大牛,必然要有扎实的基本功,不然时间再长项目再多也很难有大的提升.本系列讲的C# 高级知识点,是非常值得去撑握的,不仅可以让你写代码时游刃有余,而且去研究和学习一些开源项目时,也不会显得那么吃力了. 希望大家记住,这里讲的所有的知识点,不仅仅是了解了就可以了,还要会灵活用,一定要多思考,撑握其中的编程思想. 本文讲的是委托和事件,这两个词可能你早就耳熟能详,但你是否真正撑握了呢? 本系列讲的C#高级知识点都是要求开发时能达到可以徒手写出来的水平(不依赖搜索引擎.找笔记等

委托与事件

委托在底层就是一个函数的指针,委托是事件的基础. 你可以传递引用类型.值类型.但是你有没有需要传一个方法呢?传方法的过程就是委托. 消息类: public class Message { /// <summary> /// 传引用类型 /// </summary> /// <param name="msg"></param> public static void Send(string msg) { Console.WriteLine(&

C#学习(一):委托和事件

预备知识 在学习委托和事件之前,我们需要知道的是,很多程序都有一个共同的需求,即当一个特定的程序事件发生时,程序的其他部分可以得到该事件已经发生的通知. 而发布者/订阅者模式可以满足这种需求.简单来说,在这种模式中,发布者定义了一系列程序的其他部分可能感兴趣的事件.其他类可以"注册",以便再这些事件发生时发布者可以通知它们.这些订阅者类通过向发布者提供一个方法来"注册"以获取通知.当事件发生时,发布者"触发事件",然后执行订阅者提交的所有事件.