C#编程语言之委托与事件(一)—— C/C++函数指针和C#委托初步

  相信正在学习C#的人都有学习过C或C++的经验,本文要讲的第一个要点是C#中的委托(delegate,有些资料也叫表明)。什么是委托,不少人都能天然而然地想到C/C++中的函数指针,事实上不少书和资料都以此来引出C#中委托的概念,在此我建议若是没有接触过C/C++的同窗能够先了解一下相关的知识再来继续C#的学习,毕竟做为编程语言的基础,语言都是招式,思惟想法才是内功。有了扎实的基础,后期学习起来才可以事半功倍。ios

  首先咱们经过一个简单的例子快速复习一下C/C++函数指针:编程

 1 #include<iostream>
 2 using namespace std;  3 int func(string name){  4     cout<<"My name is "<<name<<endl;  5 }  6 void call(int(*fun)(string)){  7     fun("Evan Lin");  8 }  9 int main(int args,char ** argv){ 10  call(func); 11 }

  重点是 int(*fun)(string) 这个语句,指定一个函数指针的形参,就如同咱们定义一个变量 char ch 同样,但要求是此处函数指针的返回值和参数列表都必须与即将传进来的函数地址严格匹配,否则会产生[Invalid Conversion Error],且此处的声明方式只能用指针,即这个 (*fun) ,由于事实上函数指针只是经过一个函数的入口去操做一个函数,虽然能够经过 typedef int (fun)(string); fun *fp 的方式去表示一个函数,但最终也是要定义一个函数的指针,因此此处没法不经过指针而去调用一个函数,至少目前阶段我没有了解到,有了解的朋友能够说出来共同探讨。编程语言

  C#中的委托和C/C++中函数指针的对比函数

  一、C/C++函数指针是经过寻找函数的入口来调用一个函数,C#委托是把函数名当作一个参数传入一个委托对象当中,委托是类型,函数指针是指针。学习

  二、C/C++函数指针的返回类型和参数列表是做为匹配函数参数的标志,而C#委托有签名(Signature)的概念。spa

  三、C/C++函数指针直接操做内存的某个地址,而C#委托托管在.Net Framwork下,是一种强类型指针

  委托的签名(Signature)由委托的返回类型和参数列表组成,看起来和C/C++函数指针的返回类型和参数列表并没有多大区别,但做为一门强大的语言,委托的签名的做用不只仅是做为一种限定做用,其余做用会由下文说起。下面咱们循序渐进地一步步来认识委托(delegate)code

  1、下面是一则例子用于介绍委托的使用方法对象

 1 using System;
 2 namespace ConsoleApplication {
 3     class DelegateTest {
 4         public delegate void myDelegate(string name);
 5         public static void func(string name) {
 6             Console.WriteLine("My name is " + name);
 7         }
 8         static void Main() {
 9             myDelegate _myDe = new myDelegate(DelegateTest.func);
10             _myDe("Evan Lin");
11         }
12     }
13 }

  使用关键字delegate声明一个委托类型,声明形式主要是【delegate + 返回类型 + 委托名 + 参数列表】blog

  像普通类型同样定义一个委托变量,生成委托对象时必须把签名相应的函数做为参数传入委托对象当中,而后进行调用。

  2、委托的快捷语法,能够直接把函数名赋值给委托变量

1 myDelegate _myDe = DelegateTest.func;
2 _myDe("Evan Lin");

  委托和函数(与签名相应)之间存在着隐式转换

  3、多播(Multicast)委托

  多播委托表示能够经过+=和-=的运算符号来添加或者删除到委托队列当中,当执行这个委托的时候会按依次执行添加到委托队列当中的全部委托,当使用多播委托时,委托的返回类型必须为void,不然运行时只会执行最后一个添加到委托队列的委托。

  此处须要注意一点,当添加两个相同的函数时,在委托队列当中实质上添加了两个委托,但当减去一个委托时,若是该委托实体在委托队列中存在时,则把这份委托删除,但若是该委托实体在委托队列中不存在时,委托队列不作任何改变,且不会发生编译时异常。当委托队列为空,而后执行这个多播委托时,会抛出NullReferenceException。

  下面看一小段代码加以理解:

 1 using System;
 2 namespace ConsoleApplication1 {
 3     class DelegateTest {
 4         public delegate void AnimalDelegate();
 5         public static void Cat() {
 6             Console.WriteLine("Miao Miao");
 7         }
 8         public static void Dog() {
 9             Console.WriteLine("Wang Wang");
10         }
11         static void Main() {
12             AnimalDelegate _aniD;
13             AnimalDelegate _catD = new AnimalDelegate(DelegateTest.Cat);
14             AnimalDelegate _dogD = new AnimalDelegate(DelegateTest.Dog);
15             _aniD = _catD + _dogD;
16             _aniD();//Miao Miao \n Wang Wang
17             _aniD -= _catD;
18             _aniD();//Wang Wang
19         }
20     }
21 }

  运行会依次打印“Miao Miao”和“Wang Wang”两行结果,而后再打印“Wang Wang”。

  4、匿名方法和Lambda表达式

  能够注意到使用委托真正起到做用的仅仅是委托的签名,为了提升开发效率,因而有了匿名方法(= =纯属猜测,欢迎斧正),具体实现方法以下:

 1 using System;
 2 namespace ConsoleApplication1 {
 3     class DelegateTest {
 4         public delegate String MyDelegate(int arg);
 5         static void Main() {
 6             MyDelegate _myDe = delegate (int arg) {
 7                 return arg > 0 ? "More than zero" : "Less than or equals zero";
 8             };
 9             Console.WriteLine(_myDe(0));
10             Console.WriteLine(_myDe(1));
11         }
12     }
13 }

  如代码所示,用【delegate关键字+参数列表+方法体】构成一个委托匿名方法,此处隐藏了具体的函数名称,匿名方法的返回值无关紧要(根据委托签名),函数体的反花括号后要加分号,而后使用正常方法调用委托。说到这里相信你们均可以猜测到实际的输出结果,分别是Less than or equals zero和More than zero两行结果。

  Lambda表达式具备比较特殊的写法,一样是为了提升开发效率,下降函数名的重复率等缘由,如下经过一个实例进行了解:

 1 using System;
 2 namespace ConsoleApplication {
 3     class DelegateTest {
 4         public delegate String MyDelegate(int arg);
 5         static void Main() {
 6             MyDelegate _myDe = (arg) => {
 7                 return arg > 0 ? "More than zero" : "Less than or equals zero";
 8             };
 9         }
10     }
11 }

  实际效果等同于上一个匿名方法,在Lambda表达式中连参数类型都省去了,由于在定义一个委托类型的时候已经限定了委托的参数类型,以以上代码为例,其中参数arg的类型必须是int,返回类型必须是String。

  5、委托泛型

  若是对应于不一样的函数返回类型和函数参数列表,须要声明大量不一样签名的委托。泛型委托的出现是为了能适应不一样类型的函数,提升代码的复用率,如下经过一个简单的例子来加深理解。

 1 using System;
 2 namespace ConsoleApplication {
 3     class DelegateTest {
 4         public delegate T1 myDelegate<T1, T2>(T1 arg1, T2 arg2);
 5         public static string func1(string name,int num) {
 6             return "My name is " + name + ",and my favorite number is " + num;
 7         }
 8         static void Main() {
 9             myDelegate<string, int> _myDe = func1;
10             Console.WriteLine(_myDe("Evan Lin",13));
11         }
12     }
13 }

  其中的<T1,T2>表明两种自定义类型,同时分别做为委托的两种类型的参数,而且该委托返回T1类型的返回值。经过 myDelegate<string, int> _myDe 来限定<T1,T2>的具体类型。

  当想定义一个泛型委托,但又想在类型方面作一些限制,能够用到where关键字

  泛型委托约束大约包括几种形式:

1 public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA
2 public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA,InterfaceA
3 public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA where T2:ClassB
4 public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:T2

  where后面的表达式表明类型T1只能派生于ClassA类或者是ClassA自己,T2同理。而ClassA,ClassB,InterfaceA等也有一些限制条件,官方文档的解释是:A type used as a constraint must be an interface,a non-sealed class or a type parameter。也就是说做为限制条件的只能是某个接口,非密封(non-sealed)类或者某个参数的类型(即第四句语句所示),除此以外的类型都不能做为泛型约束的类型,不然回显示Invalid constraint错误。

  至此,以上均是我的学习C#委托时候的拙见,不免会有纰漏和不妥之处,欢迎指出斧正。

相关文章
相关标签/搜索