昨天在和一位朋友讨论到委托与接口的问题,一开始我觉得很不可思议,这两个东西的概念怎么会混淆呢?要混淆也是接口和抽象类,委托和事件相混淆啊!但是着我的一个例子我马上意识到很有可能因为我将要表现的这个例子,让很多朋友混淆了委托与接口的用途.所以我想通过这篇文章试图说明白委托和接口的概念和用途,其实他们俩的差别还是很大的.
本文适合对委托和接口概念非常了解的朋友,并且欢迎各位朋友与Snake一起探讨有关这方面的知识. 本文不适合对委托和接口概念或用途了解一知半解(模糊)的朋友,这篇文章可能会对您产生误导,请千万别看. (本文原文是一篇没有好好排版过的email,我这里将会部分摘抄,部分改进,如果有什么地方您觉得莫名其妙,我将非常感谢您的指正!)
在文章正式开始之前我需要将MSDN上对委托和接口的内容放上来,作为文章之基.
委托:
委托是一种定义方法签名的类型。当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联。您可以通过委托实例调用方法。
委托用于将方法作为参数传递给其他方法。事件处理程序就是通过委托调用的方法。您可以创建一个自定义方法,当发生特定事件时某个类(例如 Windows 控件)就可以调用您的方法.
委托具有以下特点:
委托类似于 C++ 函数指针,但它们是类型安全的。
委托允许将方法作为参数进行传递。
委托可用于定义回调方法。
委托可以链接在一起;例如,可以对一个事件调用多个方法。
方法不必与委托签名完全匹配。有关更多信息,请参见在委托中使用变体(C# 和 Visual Basic)。
C# 2.0 版引入了匿名方法的概念,此类方法允许将代码块作为参数传递,以代替单独定义的方法。C# 3.0 引入了 Lambda 表达式,利用它们可以更简练地编写内联代码块。匿名方法和 Lambda 表达式(在某些上下文中)都可编译为委托类型。这些功能统称为匿名函数。有关 Lambda 表达式的更多信息,请参见Anonymous Functions (C# Programming Guide)。
接口:
接口描述的是可属于任何类或结构的一组相关功能。接口可由方法、属性、事件、索引器或这四种成员类型的任意组合构成。接口不能包含字段。接口成员一定是公共的。
当类或结构继承接口时,意味着该类或结构为该接口定义的所有成员提供实现。接口本身不提供类或结构能够以继承基类功能的方式继承的任何功能。但是,如果基类实现接口,派生类将继承该实现。
类和结构可以按照类继承基类或结构的类似方式继承接口,但有两个例外:
类或结构可继承多个接口。
类或结构继承接口时,仅继承方法名称和签名,因为接口本身不包含实现。
接口具有下列属性:
接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。
不能直接实例化接口。
接口可以包含事件、索引器、方法和属性。
接口不包含方法的实现。
类和结构可从多个接口继承。
接口自身可从多个接口继承。
正文开始
在写这些文字的时候我又将以上的各个概念熟悉了一遍,以防自己把自己忽悠混淆了.所以不适合群众请尽快退散.另外如果您看完上面的定义和特征后就从两者的混淆中走了出来,您也可以尝试继续往下看.
首先,关于委托的用法,我们可以这样使用:
以下是代码片段:
- public int Calculate(Func del) { int a = 1, b = 2; return del(a, b); }
我们可以通过传不同的Func来改变整个方法的结果.
以下是代码片段:
- public int Add(int a, int b)
- { return a + b; }
- public int Sub(int a, int b)
- { return a - b; }
- //调用方法如下
- public void TestMethod()
- { int result = Calculate(Add);
- //the result is 3 int anotherResult = Calculate(Sub);
- //the result is -1
- }
首先我在Calculate方法中已经确定了2个数的值,并且包括在该方法当中.在输出结果的时候能明显看出传递的委托不同,其结果也不同.我们使用委托来改变方法的执行内容,我们不但可以改变其方法的内容,也可以在执行该方法的时候顺便做点什么(比如说做个日志记录).
噢,可能您觉得二者容易混淆的地方在于..我还是举个例子比较好解释.
以下是代码片段:
- public interface ICal
- { int Calculate(int a, int b); }
- //有多个类实现了ICal接口.
- public class Add : ICal
- { public int Calculate(int a, int b)
- { return a + b; } } public class Sub : ICal
- { public int Calculate(int a, int b)
- { return a - b; } }
- //然后通过调用不同类来获取不同的方法
- public static void Main()
- {
- ICal cal = new Add();
- //ICal=new Sub();
- Console.Write(cal.Calculate(1, 2));
- }
讲解一下,通过上面的例子我们可以知道在创建一个具有计算功能(Calculate)的接口ICal之后,产生了两个具有计算功能的具体类,分别是Add和Sub.为了要获得结果,我们创建了一个需要有计算功能的”坑”,并赋予能与此”坑”相匹配的类Add(或Sub),***从该坑中调用Calculate的结果就行.
貌似说的过去?好,那么我至少要让你觉得有个适用范围吧!看下面的例子.
比如有个Person类的数组arr.这时候我们可以通过委托的方法实现arr的排序.可是系统怎么知道2个Person哪个排在前面,哪个该排在后面?这时候我们就可以传进一个委托来告诉系统Peron类的大小.
以下是代码片段:
- arr.Sort(p => { p.ID });
该lambda表达式意思是丢给该Sort方法一个排序的Key(此key能够进行大小比较),那么Sort就可以根据此key来进行比较.那通过接口呢?首先得创建一个继承自IComparer的类,我就拿本身继承它吧.
好吧,它本来是很麻烦的:
以下是代码片段:
- private int SortDelegate(Person p)
- { return p.ID; }
- public void TestMethod()
- { arr.Sort(new Func(SortDelegate));
- }
但是我们要承认C# 3.0带给我们的便利.
现在,我们要让Person类实现接口的规定.
以下是代码片段:
- public int Compare(Person x, Person y)
- {
- //假设person的ID是int类型
- return x.ID - y.ID;
- }
那么我们的实现方法就可能是这样:
以下是代码片段:
- arr.Sort((new Person() as IComparer) comp);
不能再继续举例子了,我承认我忽悠您了.这些看上去都可以的实现方法有本质的区别!
首先我们看***个委托例子:在Calculate时我们的委托被允许使用了该方法内的两个变量a,b从而改变了整个方法的结果.在整个过程中委托时很被动的,因为它不知道自己会在什么时候被触发.上面的例子很简单,使您没有这种感觉,而且前面说过在方法执行的时候当委托被触发我们可以干点别的,比如说做个日志记录什么的,此时接口有能力又不破坏方法本身运行结构,又能做日志记录吗?显然实现了接口的类只能重写一遍该方法.
路人甲:那我在接口的实现中再调用一下原方法,***在方法的前面或后面加入日志记录功能不就完了吗?
Snake:杀鸡焉用宰牛刀?且不说再原方法的可行性,就算可行了,麻烦不说,万一这个方法执行有多个阶段,每个阶段都要日志记录呢?委托能深入方法,并且由方法控制它安放之地,让委托能起到关键作用,此时作为接口大哥的牛刀也剔不干净鸡骨上的肉哟~.
其次说接口的优点.我们前面可以看到委托能深入方法,也就是说委托的关注群体是方法们,而接口关注的群体则是类们.接口让类必须实现相同签名的方法或属性,以便在程序中通过调用可变的方法.既然是因为类的关系,那么它的方法肯定是不可变的了,每个实现了该接口的类,即便功能差不多也要完完全全写一遍,但是类的地盘大,肚子里的墨水多,虽然在Add类中通过ICal可调用的方法也就一个Calculate(),但是在Calculate始终是Add类的子民,所以该Calculate方法可以调用Add类中所有能调用的资源.而如果是Sub类的话,它的子民Caculate可调用的资源又与Add类不尽相同,毕竟同是Calculate,国籍不同,文化和生活方式也不同嘛,哈哈.
而接口的能力却是委托所不能企及的地方.它只能被方法藏在伸出,方法外一片蓝天而它却无能为力.如果让类比作一个国家,方法比作一个人,那么委托不就是深藏在人大脑内的处理方式的思维吗?不同的人,思维可以变,当乡下人看到城市中的高楼大厦不禁感叹,可乡下人在城市中生活习惯之后,高楼大厦又能怎样,他早已习以为常.
***的论点有点晦涩,前面的例子具有误导性,所以本篇文章需要读懂个人认为不是很容易,毕竟个人对于表达能力还是比较不自信的.希望各位同仁海涵.
【编辑推荐】