目录结构:

contents structure [+]

1.委托语法

本文会详细阐述委托的使用,以及实现,想必读者已经知道了函数编程中的回调机制,C#为回调机制提供了一种简便语法,这就是委托。
在使用委托之前需要使用delegate关键字进行声明:
比如,

delegate void MyDelegate();

上面定义了一个无参数,无返回的委托。定义好后,然后就可以声明委托对象。

MyDelegate myDelegate=new MyDelegate(SomeStaticMethod);

上面创建了一个委托对象,这个委托对象关联了一个方法参数。
C#为这种操作提供了一种糖语法,就是不需要构造委托对象,如下:

MyDelegate myDelegate=SomeStaticMethod;

示例:

   class Program
{
delegate void MyDelegate();
static void Main(string[] args)
{
MyDelegate myDelegate = Method1;//委托静态方法Method1
myDelegate += new Program().Method2;//委托实例方法Method2
myDelegate.Invoke();//调用委托链上的所有方法 Console.ReadKey();
}
static void Method1() {
Console.WriteLine("method1 invoked");
}
void Method2() {
Console.WriteLine("method2 invoked");
}
}

2.泛型委托

C#允许泛型委托,目的是保证任何类型的对象都可以以类型安全的方式传给回调方法。
例如:

public delegate TResult CallMe<TResult,TKey,TValue>(TKey key,TValue value);

委托的每个泛型类型参数都可以标记为协变量和逆变量。

在这里介绍一下泛型类型参数的三种变量:
不变量(invariant):意味着泛型类类型参数不能更改。
逆变量(contravariant):意味着泛型类型参数可以从一个类更改为它的某个派生类,C#中使用in关键字标记逆变量形式的泛型类型参数。逆变量泛型类型参数T只能出现在输入位置,作为参数。
协变量(convariant):意味着泛型类型参数可以从一个类更改为它的某个基类,C#中使用out关键字标记协变量形式的泛型类型参数。逆变量泛型类型参数只能出现在输出位置,作为返回类型。

例如存在以下泛型类型委托:

public delegate TResult MyFunc<in T,out TResult>(T arg);

如果像下面定义一个委托变量:

MyFunc(Object,ArgumentException) fn1=null;

然后就可以将它转变为其他委托类型变量:

MyFunc(String,Exception) fn2=fn1;//转化
fn2("");//调用

3.委托链

C#中提供了一种委托链的操作,通过委托链可以关联多个方法。
C#在实现委托链的时候使用了组合设计模式。C#中使用Delegate类的Combine类来完成委托的链接,例如:

    class Program
{
delegate Int32 Max(Int32 a, Int32 b);
static void Main(string[] args)
{
Max max1 = new Max(new Program().Method1);//定义委托max1
Max max2 = new Max(new Program().Method2);//定义委托max2
Delegate max = Delegate.Combine(max1, max2);//组合委托链
Object obj= max.DynamicInvoke(,);//动态调用
Console.WriteLine(obj);//
Console.ReadLine();
}
Int32 Method1(Int32 a, Int32 b) {
return a > b ? a : b;
}
Int32 Method2(Int32 a, Int32 b) {
if (a > b) {
return a;
}
return b;
}
}

上面我们使用Delegate的Combine方法合并为委托链,其实Delegate类还提供了许多操作方法,例如:

Delegate.remove(Delegate,Delegate);//从第一个委托链中移除第二个委托链中的委托
Delegate.DynamicInvoke(Object[]);//调用委托链中的所有方法
static Delegate CreateDelegate(Type type,MethodInfo method)//从给定的委托类型中,创建一个静态方法委托

在C#中还给Delegate重载了+=和-=操作符,可以更方便的操作委托链,比如:

            Max max1 = new Max(new Program().Method1);//定义委托max1
Max max2 = new Max(new Program().Method2);//定义委托max2
max1 += max2;

4.lambda表达式

Lambda表达式是C#3.0才添加的功能,是为委托添加的糖语法,lambda表达式允许以内联的方式写回调方法的代码,而不需要单独写到方法中,例如:

        delegate Int32 Max(Int32 a, Int32 b);
static void Main(string[] args)
{
Max m = null;
m += (a, b) => {
return a > b ? a : b;
};//lambda表达式
Int32 res= m(,);//调用方法
Console.WriteLine(res);
Console.ReadLine();
}

lambda表达式的大致格式为:(参数...)=>{方法主体}
通常我们在写线程的时候,我们都会定义一个WaitCallback委托,然后再关联方法,现在我们可以像如下这样:

            ThreadPool.QueueUserWorkItem((obj) => {
Console.WriteLine("Thread:" + Thread.CurrentThread.ManagedThreadId);
});
Console.ReadLine();

关于使用委托,这里有一个不成文规定,通常若是需要在回调方法写3行以上的代码就不用lambda表达式,而是重新定义一个方法,并为其分配名称。若小于等于3行代码,就可以使用lambda表达式。

例如:

            //创建并初始化一个String数组
String[] names = { "Jeff","Kristin","Aidan","Grant"}; //获取只含有小写字母a的名称
Char charTOFind='a';
names = Array.FindAll(names, (name) => { return name.IndexOf(charTOFind) >= ; }); //将每个字符串的名称转化为大写
names= Array.ConvertAll(names, (name) => { return name.ToUpper(); }); //打印
Array.ForEach(names,System.Console.WriteLine);

5.揭秘委托

到现在我们已经知道了如何定义委托,以及C#为委托提供的糖语法,接下来我们开始探讨一下委托究竟是什么,将如下的代码编译为二进制文件,

    class Program
{
delegate Int32 MathAdd(Int32 a, Int32 b);
static void Main(string[] args)
{
MathAdd mathAdd = new MathAdd((Int32 a, Int32 b) => { return a + b; });
Int32 result= mathAdd(,);
Console.WriteLine(result);//
Console.ReadKey();
}
}

然后我们使用ildasm.exe反编译应用程序得到il文件,就可以从CLR层面观察出C#的委托到底是怎么回事了。

通过这张图片我们可以清晰的观察出,IL代码生成了一个静态内部类MathAdd,然后该类派生于MulticastDelegate,到现在我们清楚了C#的委托本质上就是类。除此之外,该类提供了一个构造器方法,BeginInvoke方法,EndInvoke方法,Invoke方法。

6.类库中的委托

C#已经预先定义好了丰富的委托类型供开发人员使用,在MSCorLib.dll中,有将近50个委托类型,
例如:

public delegate void TryCode(object userData);
public delegate void WaitCallback(object state);
public delegate void TimerCallback(object state);
public delegate void ContextCallback(object state);
......

上面这些委托都有一个共同点,就是他们都是一样的,也就是说,只需要定义一个委托就可以了。
.NET Framework支持泛型,C#已经为我们定义好了17个Action委托,其中包含16个泛型委托:

public delegate void Action();
public delegate void Action<T>(T arg);
public delegate void Action<T1,T2>(T1 arg1,T2 arg2);
public delegate void Action<T1,T2,T3>(T1 arg1,T2 arg2,T3 arg3);
......
public delegate void Action<T1,.....,T16>(T1 arg1,......,T16 arg16);

除了Action委托,C#还为我们定义了Func委托,允许回调方法返回值:

public delegate TResult Func<TResult>();
public delegate TResult Func<T,TResult>(T arg);
public delegate TResult Func<T1,T2,TResult>(T1 arg1,T2 arg2);
public delegate TResult Func<T1,T2,T3,TResult>(T1 arg1,T2 arg2,T3 arg3);
......
public delegate TResult Func<T1,.....,T16,TResult>(T1 arg1,.....,T16 arg16);

上面列出了C#定义的Action和Func委托,如果涉及到委托最好是使用这些委托类型,而不是重新定义,这样可以减少程序集的大小。

但是如果涉及到ref或out参数,那么就只有自己定义了。
比如:

delegate void Bar(ref Int32 z);

7.委托和反射

通常情况下,使用委托都应该知道委托的原型,但是反射提供了另一种可能来使用委托。
在MethodInfo中提供了一个createDelegate方法,运行在编译器不知道委托的所有信息下提前创建委托。

        public virtual Delegate CreateDelegate(Type delegateType);
public virtual Delegate CreateDelegate(Type delegateType, object target);

在创建好Delegate对象后,就可以通过调用对象的DynamicInvoke方法来调用委托。

    public Object DynamicInvoke(params Object[] args);

下面的展示了如何使用反射来调用委托:

    class Program
{
static void Main(string[] args)
{
//定义一个Func<Int32,Int32,Int32>类型的数据
Type type=typeof(Func<Int32,Int32,Int32>);
//获得Program实例的Max方法
MethodInfo methodInfo = typeof(Program).GetMethod("Max", BindingFlags.Instance|BindingFlags.Public);
//创建委托
Delegate d = methodInfo.CreateDelegate(type, new Program());
//调用
Object res = d.DynamicInvoke(, );
Console.WriteLine(res);//
Console.ReadKey();
}
public Int32 Max(Int32 a, Int32 b)
{
return a > b ? a : b;
}
}

【C#】详解C#委托的更多相关文章

  1. 详解C#泛型(二) 获取C#中方法的执行时间及其代码注入 详解C#泛型(一) 详解C#委托和事件(二) 详解C#特性和反射(四) 记一次.net core调用SOAP接口遇到的问题 C# WebRequest.Create 锚点“#”字符问题 根据内容来产生一个二维码

    详解C#泛型(二)   一.自定义泛型方法(Generic Method),将类型参数用作参数列表或返回值的类型: void MyFunc<T>() //声明具有一个类型参数的泛型方法 { ...

  2. 详解C#委托,事件与回调函数

    .Net编程中最经常用的元素,事件必然是其中之一.无论在ASP.NET还是WINFrom开发中,窗体加载(Load),绘制(Paint),初始化(Init)等等.“protected void Pag ...

  3. 详解C#委托和事件(二)

    一.当我们使用关键字delegate声明一个自定义委托类型时,实际上是声明了一个该名称的类类型,继承自抽象类System.MulticastDelegate,还包含实例方法Invoke.BeginIn ...

  4. 详解C#委托和事件(一)

    委托(Delegate)是安全封装方法的类型,类似于C和C++中的函数指针,与函数指针不同的是,委托是面向对象的.类型安全的和可靠的: 一.委托类型是CTS中五种基础类型之一,是一种引用类型,表示对具 ...

  5. C#委托详解(3):委托的实现方式大全(续)

    接上篇(C#委托详解(2):实现方式大全),本篇继续介绍委托的实现方式. 4.Action<T>和Func<T>委托 使用委托时,除了为每个参数和返回类型定义一个新委托类型之外 ...

  6. C#委托详解(2):实现方式大全

    本系列文章将详细探讨C#中的委托,列举其主要的实现方式,并分析其在设计层面和编码层面带来的好处,最后会讨论其安全性和执行效率等. 接上篇(C#委托详解(1):什么是委托)介绍完什么是委托之后,来看看C ...

  7. 详解Objective-C中委托和协议

    Objective-C委托和协议本没有任何关系,协议如前所述,就是起到C++中纯虚类的作用,对于“委托”则和协议没有关系,只是我们经常利用协议还实现委托的机制,其实不用协议也完全可以实现委托. AD: ...

  8. C#事件与委托详解

    from https://www.cnblogs.com/sjqq/p/6917497.html C#事件与委托详解[精华 多看看] Delegatedelegate是C#中的一种类型,它实际上是一个 ...

  9. 委托与事件代码详解与(Object sender,EventArgs e)详解

    委托与事件代码详解 using System;using System.Collections.Generic;using System.Text; namespace @Delegate //自定义 ...

随机推荐

  1. oracle连接错误:ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务解决

    自己的解决办法是,把数据库连接字符串的默认SID_NAME = ORCL改为 sid_name =test(自己安装数据库时候改的名字).即可正常连接. 网上搜罗的其他问题:把监听服务重启下.(自己的 ...

  2. c++面向过程和面向对象-C++编译器是如何管理类和对象的

    1,c++编译时如何区分对象调用类的方法? C++中的class从面向对象理论出发,将变量(属性)和函数(方法)集中定义在一起,用于描述现实世界中的类.从计算机的角度,程序依然由数据段(栈区内存)和代 ...

  3. js回调函数,检测这个值是否重复

    //校验提交的数据是否重复 /** * url:后端的查询地址 * filedVal: 要传到后台的值 * ele:要绑定显示的元素,一般就是当前的input就可以,直接在其后边追加显示 * fn:回 ...

  4. Codeforces Beta Round #59 (Div. 2)

    Codeforces Beta Round #59 (Div. 2) http://codeforces.com/contest/63 A #include<bits/stdc++.h> ...

  5. 有关defer和async的区别

    关于async.defer功能及异同的介绍 async属性会让js并行加载,并在js加载完成后立即执行,也就是说执行顺序由加载速度定,而不是html中的先后顺序 defer属性js同样会并行加载,而执 ...

  6. [leetcode]449. Serialize and Deserialize BST序列化反序列化二叉搜索树(尽量紧凑)

    Serialization is the process of converting a data structure or object into a sequence of bits so tha ...

  7. 编程,计算data段中的第一组数据的3次方,结果保存在后面一组dword单元中

    assume cs:code data segment dw ,,,,,,, dd ,,,,,,, data ends code segment start: mov ax,data mov ds,a ...

  8. php 多进程 父进程的阻塞与非阻塞

    php中进程的阻塞,主要是父进程等待子进程退出. 1.php代码如下: <?php //定义进程数量 define('FORK_NUMS', 5); //用于保存进程pid $pids = ar ...

  9. 使用JFinal实现使用MVC获取表单中的数据并将提示信息返回给另一jsp页面。

    1.包结构 2.我们需要对web.xml进行配置: <?xml version="1.0" encoding="UTF-8"?> <web-a ...

  10. 11.1JS笔记

    1.js的数据类型分为基本和引用,基本(string.undefined.null.number,boolean),引用(object-->array.function.json等) 2.基本数 ...