委托和事件 (1)_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > 委托和事件 (1)

委托和事件 (1)

 2015/1/31 16:38:49  委托简析  程序员俱乐部  我要评论(0)
  • 摘要:个人认为,c#最重要的精髓在于其委托。说实话现在已经是c#5.0的时代,c#6很快也要出来了,委托作为一个c#1就有的性质,已经早就被更高级的工具例如泛型委托,lambda表达式包装起来了,基本上已经很少有人会在程序中声明一个delegate。不过,了解一下基础也是很好的,基本概念委托是一个特殊的类(密封类),可以被视为函数指针,其代表一类和委托签名的输入输出变量类型和个数相同的方法。委托本身可以作为变量传入方法。借用经典的greetPeople例子,在实际工作中,总会遇到类似的情况
  • 标签:事件

个人认为,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. +=和-=是caozuofu.html" target="_blank">操作符的重载,本质是调用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); } }

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

上一篇: CCTransition类--Cocos2D-Swift v3.3 下一篇: 没有下一篇了!
发表评论
用户名: 匿名