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

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

 #include<iostream>
using namespace std;
int func(string name){
cout<<"My name is "<<name<<endl;
}
void call(int(*fun)(string)){
fun("Evan Lin");
}
int main(int args,char ** argv){
call(func);
}

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

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

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

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

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

  委托的签名(Signature)由委托的返回类型和参数列表组成,看起来和C/C++函数指针的返回类型和参数列表并无多大区别,但作为一门强大的语言,委托的签名的作用不仅仅是作为一种限定作用,其他作用会由下文提及。下面我们按部就班地一步步来认识委托(delegate)

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

 using System;
namespace ConsoleApplication {
class DelegateTest {
public delegate void myDelegate(string name);
public static void func(string name) {
Console.WriteLine("My name is " + name);
}
static void Main() {
myDelegate _myDe = new myDelegate(DelegateTest.func);
_myDe("Evan Lin");
}
}
}

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

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

  二、委托的快捷语法,可以直接把函数名赋值给委托变量

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

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

  三、多播(Multicast)委托

  多播委托表示可以通过+=和-=的运算符号来添加或者删除到委托队列当中,当执行这个委托的时候会按依次执行添加到委托队列当中的所有委托,当使用多播委托时,委托的返回类型必须为void,否则运行时只会执行最后一个添加到委托队列的委托。

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

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

 using System;
namespace ConsoleApplication1 {
class DelegateTest {
public delegate void AnimalDelegate();
public static void Cat() {
Console.WriteLine("Miao Miao");
}
public static void Dog() {
Console.WriteLine("Wang Wang");
}
static void Main() {
AnimalDelegate _aniD;
AnimalDelegate _catD = new AnimalDelegate(DelegateTest.Cat);
AnimalDelegate _dogD = new AnimalDelegate(DelegateTest.Dog);
_aniD = _catD + _dogD;
_aniD();//Miao Miao \n Wang Wang
_aniD -= _catD;
_aniD();//Wang Wang
}
}
}

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

  四、匿名方法和Lambda表达式

  可以注意到使用委托真正起到作用的仅仅是委托的签名,为了提高开发效率,于是有了匿名方法(= =纯属猜想,欢迎斧正),具体实现方法如下:

 using System;
namespace ConsoleApplication1 {
class DelegateTest {
public delegate String MyDelegate(int arg);
static void Main() {
MyDelegate _myDe = delegate (int arg) {
return arg > ? "More than zero" : "Less than or equals zero";
};
Console.WriteLine(_myDe());
Console.WriteLine(_myDe());
}
}
}

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

  Lambda表达式具有比较特殊的写法,同样是为了提高开发效率,降低函数名的重复率等原因,以下通过一个实例进行了解:

 using System;
namespace ConsoleApplication {
class DelegateTest {
public delegate String MyDelegate(int arg);
static void Main() {
MyDelegate _myDe = (arg) => {
return arg > ? "More than zero" : "Less than or equals zero";
};
}
}
}

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

  五、委托泛型

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

 using System;
namespace ConsoleApplication {
class DelegateTest {
public delegate T1 myDelegate<T1, T2>(T1 arg1, T2 arg2);
public static string func1(string name,int num) {
return "My name is " + name + ",and my favorite number is " + num;
}
static void Main() {
myDelegate<string, int> _myDe = func1;
Console.WriteLine(_myDe("Evan Lin",));
}
}
}

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

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

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

 public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA
public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA,InterfaceA
public delegate T1 myDelegate<T1,T2>(T1 arg1,T2 arg2)where T1:ClassA where T2:ClassB
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#委托时候的拙见,难免会有纰漏和不妥之处,欢迎指出斧正。

C#编程语言之委托与事件(一)—— C/C++函数指针和C#委托初步的更多相关文章

  1. Atitit java方法引用(Method References) 与c#委托与脚本语言js的函数指针

    Atitit java方法引用(Method References) 与c#委托与脚本语言js的函数指针   1.1. java方法引用(Method References) 与c#委托与脚本语言js ...

  2. C语言函数指针与 c#委托和事件对比

    C语言: 函数指针可以节省部分代码量,写类似具有多态的函数,比如要比较最大值,如果不用函数指针就只能写比较某一类型比如int类型的max函数,这个max无法比较string的大小.函数指针的意义就不多 ...

  3. VB6/VBA中跟踪鼠标移出窗体控件事件(类模块成员函数指针CHooker类应用)

    一.关于起因 前几天发了一篇博文,是关于获取VB类模块成员函数指针的内容(http://www.cnblogs.com/alexywt/p/5880993.html):今天我就发一下我的应用实例. V ...

  4. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  5. Silverlight项目笔记1:UI控件与布局、MVVM、数据绑定、await/async、Linq查询、WCF RIA Services、序列化、委托与事件

    最近从技术支持转到开发岗,做Silverlight部分的开发,用的Prism+MVVM,框架由同事搭好,目前做的主要是功能实现,用到了一些东西,侧重于如何使用,总结如下 1.UI控件与布局 常用的主要 ...

  6. c# 关键字delegate、event(委托与事件)[MSDN原文摘录][1]

    A delegate is a type that safely encapsulates a method, similar to a function pointer in C and C++. ...

  7. C#学习之初步理解委托、事件、匿名方法和Lambda

    最经在学习LinqtoSql,然后扯到Lambda表达式,然后扯到匿名方法,然后扯到委托,最后扯到事件处理...后来发现对委托这个概念和事件处理这个过程理解得不是很清晰,遂得一下学习笔记.那里说得不对 ...

  8. C#委托及事件

    转载:http://www.cnblogs.com/warensoft/archive/2010/03/19/1689806.html C#委托及事件 在C#中,委托(delegate)是一种引用类型 ...

  9. C# 基于委托的事件

    事件基于多播委托的特性. 多播委托事实上就是一组类型安全的函数指针管理器,调用则执行顺序跳转到数组里所有的函数指针里执行. class Program { public class CarInfoEv ...

随机推荐

  1. 使用ifstream和getline读取文件内容[c++]

    转载:http://www.cnblogs.com/JCSU/articles/1190685.html 假设有一个叫 data.txt 的文件, 它包含以下内容: Fry: One Jillion ...

  2. 手机端仿ios的省市县3级联动脚本一

    一,图片实例 二,代码 2.1,代码 $('#provinceCity_fu').click(function(){ var $this = $(this); new Picker({ "t ...

  3. 主流nosql数据库对比

    目前开源的NOSQL数据库有,Redis,Tokyo Cabinet,Cassandra,Voldemort,MongoDB,Dynomite,HBase,CouchDB,Hypertable, Ri ...

  4. 【CJOJ2484】【Luogu2805】最小函数值(函数最小值)

    题面 Description 有n个函数,分别为F1,F2,...,Fn.定义 Fi(x)=Aix2+Bix+Ci(x∈N∗)Fi(x)=Aix2+Bix+Ci(x∈N∗) .给定这些Ai.Bi和Ci ...

  5. 【ZJOI2008】树的统计(树链剖分)

    题面 Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II ...

  6. 版本控制-Git对象

    Git对象 版本控制在于文件的控制,git的控制方法在于为每个文件生成(key,object)的结构.git利用sha-1加密算法,对每一个文件生成一个唯一的字符序列(明文大小不超过2^64位,对于普 ...

  7. MySQL的sum()函数

    如下图,这是一个关于用户参加活动,每个活动会给这位用户评分的一个表: 用户1参加了A活动,评分100: 用户2参加了B活动,评分98,又参加了D活动,评分10: 用户3参加了C活动,评分99 需求:把 ...

  8. 在linux上安装dotnetcore

    dotnet core已经出来有一段时间了,不是什么新名词了.但这个技术,目前还是比较新的,企业也没有普遍应用.它最大的亮点就是跨平台,也就是我们写的c#代码,可以运行在linux上. 在国内学习do ...

  9. mvc-dispatchar-servlet.xml文件报错

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  10. JS中的闭包问题

    一.闭包:在函数外也可使用局部变量的特殊语法现象 全局变量 VS 局部变量: 全局变量:优点:可共享,可重用; 缺点:在任意位置都可随意修改——全局污染 局部变量:优点:安全 缺点:不可共享,不可重用 ...