C#中的委托和事件

C#中的委托和事件

 

前言

 

委托和事件在.NET框架中应用的很广泛,但是楼主因为知识尚浅,用到的不多,但是听前辈们说,这个委托和事件就像两座大山,只要翻过了这两座大山,.NET的知识可能就会一通百通了.最近几天咱们就一起来说说什么是委托,为啥要用委托,事件的由来,.NET框架中的委托和事件委托中方法异常和超时的处理,委托,与异步编程,委托和事件对Observer设计模式的意义.貌似很高大上呢!如果你对一个小白说出这些名词,估计小白会把你当天人来对待了!

 

 

首先理解委托

 

将方法作为方法的参数

 

先不管多么绕口,也别管委托杀个啥玩意,先看下面这两个最简单的方法,他们不过是在屏幕上输出一下:

 

        public void GreetPeople(string name)
        {
            //做某些额外的事情,比如初始化之类的
            EnglishGreetPeople(name);
        }
        public void EnglishGreetPeople(string name)
        {
            Console.WriteLine("hello : "+name);
        }
 

暂且不管这两个方法有没有实际意义,GreetPeople用于向某人问好,当传递代表某人姓名的name参数,比如”Jimmy”进去的时候,在这个方法中,将会调用EnglishGreetProple方法,再次传递name参数,EnglishGreetPeople则用于想屏幕输出”hello : Jimmy”.

 

现在如果这个程序需要进行全球化,比如中国人不明白”hello”是啥意思,怎么办呢?我们需要改程序:

        public void ChineseGreeting(string name)
        {
            Console.WriteLine("你好 : "+name);
        }


这个时候,GreetPeople也需要修改,不然如何判断使用哪个版本的问候方法合适呢?在进行这个之前,只好再定义一个枚举作为判断的依据:

        public enum Language
        {
            English,
            Chinese
        }
        public void GreetPeople(string name,Language lang)
        {
            //做某些额外的事情,比如初始化之类的
            switch (lang)
            {
                case Language.English:
                    EnglishGreetPeople(name);
                    break;
                case Language.Chinese:
                    ChineseGreeting(name);
                    break;                
            }
        }


好了,尽管这样解决了问题,但不用说大家也应该回箱单,这个方法的扩展性很差,世界上有这么多国家,如果这样的话,我们就必须一一的添加各个国家的GreetPeople()方法.

 

在考虑新的解决方案之前,先来看一下GreetPeople的方法签名:

public void GreetPeople(string name,Language lang)

我们仅仅看string name,在这里,string是参数类型,name是参数变量,当赋给name字符串”jimmy”的时候,它就代表”jimmy”这个值;当赋给它”张三”的时候,它又代表”张三”这个值.然后,就可以在方法体内对这个name进行其他操作.

 

再仔细想想,假如GreetPeople()方法可以接受一个参数变量,这个变量可以代表另一个方法,当这个变量被赋值为EnglishGreetPeople的时候,它代表EnglishGreetPeople这个方法;当赋给ChineseGreet的时候,它又代表ChineseGreet这个方法了,我们将这个参数变量命名为MakeGreeting,那么不是可以和给name赋值时一样,在调用GreetPeople()方法的时候,给这个MakGreeting参数也赋上值.然后在方法体内部,也可以像使用别的参数一样使用MakGreeting.但是,由于MakGreeting代表着一个方法,它的使用方式应该和它被赋值的方法(比如ChineseGreet)一样.说了这么多,是不是有点晕?没关系,咱们先看个案例:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace 委托和泛型2
{
    public delegate void GreetingDelegate(string name);
    class Program
    {
        static void Main(string[] args)
        {
            GreetPeople("Jimmy",EnglishGreeting);
            GreetPeople("张三",ChineseGreeting);
        }
        public static void ChineseGreeting(string name)
        {
            Console.WriteLine("你好 "+name);
        }
        public static void EnglishGreeting(string name)
        {
            Console.WriteLine("hello "+name);
        }
        private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
        {
            MakeGreeting(name);
        }     
    }
}
 


 

这个案例可能不是很清楚说明我的意思,咱们慢慢道来.这里不再需要枚举了,因为在向MakeGreeting赋值的时候动态的决定使用哪个方法,而在这个两个方法的内部,已经对使用”hello”还是”你好”.

 

本例中的委托的定义:

public delegate void GreetDelegate(string name)

咱们完全可以先和上面EnglishGreeting方法的签名对比一下,除了加入了delegate关键字以外,其余的是不是完全一样?

 

有了委托的定义,就好办了,定义一个委托其实和定义一个int类型的变量一样.

现在对委托简单的说一点,委托其实是一个类,使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态的赋给参数的做法,可以避免在程序中大量使用判断语句,同时是程序具有很好的扩展性.

 

将方法绑定到委托

 

看到这里,是不是还有点迷糊,没关系,慢慢的就明白了,上面的案例你肯定会觉得不就是输出一行语句吗,没啥实际意义,而且这样的代码更不容易让人理解.

 

上面的的例子中,不一定要直接在GreetPeople()方法中name参数赋值,可以像下面这样使用变量:

        static void Main(string[] args)
        {
            string name1, name2;
            name1 = "Jimmy";
            name2 = "张三";
            GreetPeople(name1,EnglishGreeting);
            GreetPeople(name2,ChineseGreeting);
        }


而既然委托GreetingDelegate和类型string的地位一样,都是定义了一种参数类型,那么是不是也可以像下面这样使用委托:

 

        static void Main(string[] args)
        {
            GreetingDelegate delegate1, delegate2;
            delegate1 = EnglishGreeting;
            delegate2 = ChineseGreeting;
            string name1, name2;
            name1 = "Jimmy";
            name2 = "张三";
            GreetPeople(name1,delegate1);
            GreetPeople(name2,delegate2);            
        }

这样也是没问题的,这里要说明的是委托不同于string的一个特性:可以将多个方法赋给同一个委托,或者理解为将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法.想下面这样:

        static void Main(string[] args)
        {
            GreetingDelegate delegate1;
            delegate1 = EnglishGreeting;
            delegate1 += ChineseGreeting;            
            GreetPeople("张三",delegate1);                 
        }
 


看一下输出:

hello 张三
你好 张三


 

实际上,完全可以绕过GreetPeople方法,通过委托来直接调用EnglishGreetingChineseGreeting:

            GreetingDelegate delegate1;
            delegate1 = EnglishGreeting;
            delegate1 += ChineseGreeting;
            //将先后调用EnglishGreeting与ChineseGreeting方法
            delegate1("张三");   


说明一下,这在本例中是没问题的,但回头看下上面GreetPeople()的定义,在其中可以在一些对于EnglishGreetingChineseGreeting来说都需要进行的工作,为了简便这里省了.

 

注意,这里第一次用的”=”,是赋值的语法;第二次用的是”+=”,是绑定的语法.如果第一次就是用”+=”,将出现”使用了为赋值的局部变量”的编译错误.

 

可以使用下面的代码赖建华这一过程:

        static void Main(string[] args)
        {
            GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
            delegate1 += ChineseGreeting;
            delegate1("张三");
        }


这样的话,应该能看出这样一条语句和实例化一个类是何其的相似.不禁可以想到:上面第一次绑定委托时不可以使用”+=”的编译错误,或许可以用下面的方法来避免:

        static void Main(string[] args)
        {
            GreetingDelegate delegate1 = new GreetingDelegate();
            delegate1 += EnglishGreeting;
            delegate1 += ChineseGreeting;
            delegate1("张三");
        }


但是实际上,这样会出现编译错误:”GreetingDelegate”方法没有采用”0”个参数的重载.尽管这样的结果会让人觉得有点沮丧,但是编译时的提示”没有0个参数的重载”再次让我们联想到了类的构造函数.

 

既然委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,这个语法是”-=”;

        static void Main(string[] args)
        {
            GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);            
            delegate1 += ChineseGreeting;//给此委托变量再绑定一个方法
            //将先后调用EnglishGreeting和ChineseGreeting方法
            GreetPeople("张三", delegate1);
            Console.WriteLine();
            delegate1 -= EnglishGreeting;//取消对EnglishGreeting方法 的绑定
            GreetPeople("张三",delegate1);
        }


 

小小的总结:

使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用”调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法.

 

 

委托与接口

 

在上面的例子中,使用委托定义了方法的前面,从而隔离了变化,此处所指的变化纪伟方法体内的具体实现.注意这里使用了隔离变化”这个术语,他是一个面向对象设计的原则.实际上,对于上面说的案例,委托所起到的作用和定义一个只含有一个方法的接口完全一样.下面的代码使用接口完成了与使用委托完全相同的功能:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApplication1
{
    public interface IGreeting
    {
        void GreetingPeople(string name);
    }
    public class EnglishGreeting : IGreeting
    {
        public void GreetingPeople(string name)
        {
            Console.WriteLine("hello :"+name);
        }
    }
    public class ChineseGreeting : IGreeting
    {
        public void GreetingPeople(string name)
        {
            Console.WriteLine("你好 :"+name);          
        }
    }
    class Program
    {
        private static void GreetPeople(string name, IGreeting makeGreeting)
        {
            makeGreeting.GreetingPeople(name);
        }
        static void Main(string[] args)
        {
            GreetPeople("Jimmy",new EnglishGreeting());
            GreetPeople("张三",new ChineseGreeting());
        }
    }
}
 


可以看到,在使用接口时,代码量大了很多,而且还需要定义实现了接口的类.需要注意的是,在采用接口实现时,利用的是C#的多态能力.尽管他们的结构是一样的,但是过程是不一样的.

 

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值