前言

委托其实一直以来都感觉自己应该挺熟悉的,直到最近又去翻了翻 CLR via C#,感觉我之前的理解可能还有失偏颇。在这记录一下。

之前文章的链接:

委托(Delegate)

说起委托,我们首先应该想到 回调函数 .NET-Framework 通过委托 来提供回调函数机制。委托确保回调方法是类型安全的,委托还允许调用多个方法,并支持静态方法和实例方法。

下面通过一个具体实例来分析委托的声明,创建,以及使用。

public sealed class Program {
public static void Main() {
DelegateIntro.Go();
GetInvocationList.Go();
AnonymousMethods.Go();
DelegateReflection.Go("TwoInt32s", "Add", "123", "321");
DelegateReflection.Go("TwoInt32s", "Subtract", "123", "321");
DelegateReflection.Go("OneString", "NumChars", "Hello there");
DelegateReflection.Go("OneString", "Reverse", "Hello there");
}
} internal sealed class DelegateIntro {
// Declare a delegate type; instances refer to a method that
// takes an Int32 parameter and returns void.
internal delegate void Feedback(Int32 value); public static void Go() {
StaticDelegateDemo();
InstanceDelegateDemo();
ChainDelegateDemo1(new DelegateIntro());
ChainDelegateDemo2(new DelegateIntro());
} private static void StaticDelegateDemo() {
Console.WriteLine("----- Static Delegate Demo -----");
Counter(1, 3, null);
Counter(1, 3, new Feedback(DelegateIntro.FeedbackToConsole));
Counter(1, 3, new Feedback(FeedbackToMsgBox)); // "Program." is optional
Console.WriteLine();
} private static void InstanceDelegateDemo() {
Console.WriteLine("----- Instance Delegate Demo -----");
DelegateIntro di = new DelegateIntro();
Counter(1, 3, new Feedback(di.FeedbackToFile)); Console.WriteLine();
} private static void ChainDelegateDemo1(DelegateIntro di) {
Console.WriteLine("----- Chain Delegate Demo 1 -----");
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fb3 = new Feedback(di.FeedbackToFile); Feedback fbChain = null;
fbChain = (Feedback)Delegate.Combine(fbChain, fb1);
fbChain = (Feedback)Delegate.Combine(fbChain, fb2);
fbChain = (Feedback)Delegate.Combine(fbChain, fb3);
Counter(1, 2, fbChain); Console.WriteLine();
fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));
Counter(1, 2, fbChain);
} private static void ChainDelegateDemo2(DelegateIntro di) {
Console.WriteLine("----- Chain Delegate Demo 2 -----");
Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb2 = new Feedback(FeedbackToMsgBox);
Feedback fb3 = new Feedback(di.FeedbackToFile); Feedback fbChain = null;
fbChain += fb1;
fbChain += fb2;
fbChain += fb3;
Counter(1, 2, fbChain); Console.WriteLine();
fbChain -= new Feedback(FeedbackToMsgBox);
Counter(1, 2, fbChain);
} private static void Counter(Int32 from, Int32 to, Feedback fb) {
for (Int32 val = from; val <= to; val++) {
// If any callbacks are specified, call them
if (fb != null)
fb(val);
}
} private static void FeedbackToConsole(Int32 value) {
Console.WriteLine("Item=" + value);
} private static void FeedbackToMsgBox(Int32 value) {
MessageBox.Show("Item=" + value);
} private void FeedbackToFile(Int32 value) {
StreamWriter sw = new StreamWriter("Status", true);
sw.WriteLine("Item=" + value);
sw.Close();
}
}

在上面,Feedback 委托指定的方法接受Int32参数,返回 void。

将方法绑定到委托时,C#和CLR都允许引用类型的协变性(covariance)和逆变性(contravariance)。协变性是指方法能返回从委托的返回类型派生的一个类型。逆变性是指方法获取的参数可以是委托的参数类型的基类。关于可变性和逆变性可以参考一下之前的博文接口和委托的泛型可变性

假设定义如下委托

delegate Object MyCallback(FilStram s);

完全可以构造如下的方法:

String SomeMethod(Stream s);

SomeMethod 的返回类型(String) 派生自委托的返回类型(Object)这种协变性是允许的。SomeMethod 的参数类型(Stream)是委托的参数类型(FileStream)的基类。这种逆变性是允许的。

只有引用类型才支持协变性和逆变性,值类型或void 不支持

委托揭秘

首先让我们再来看一下这个委托的声明代码:

internal delegate void Feedback(Int32 value);

编译器会做这样一件事,把上面那段代码定义成下面这一段

internal class Feedback :System.MulticastDelegate{
//构造器
public Feedback(Object @object, Intptr method); public virtual void Invoke(Int32 value); public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object @object); public virtual void EndInvoke(IAsyncResult result);
}

Feedback 类派生自FCL(Framework class library)定义的 System.MulticastDelegate 类

所有委托类型都派生自MulticastDelegate

委托类既可以嵌套在一个类型中定义,也可以在全局范围中定义。简单来说,由于委托是类,所以凡是能够定义类的地方,都能定义委托。

接下来介绍一下MulticastDelegate 中三个重要的非公共字段

  1. _target 类型为 System.Object ,当委托队形包装一个静态方法时,这个字段为null。当委托对象包装一个实例方法时,这个字段引用的是回调方法要操作的对象。换言之,这个字段指出要传给实例方法的隐式参数this 的值。
  2. _methodPtr 类型为 System.IntPtr 一个内部的整数值,CLR 用它来标识要回调的方法
  3. _invocationList 类型为 System.Object 该字段通常为 null。构造委托链时它引用一个委托数组

比如上面代码中的

Feedback fb1 = new Feedback(FeedbackToConsole)

那么这个 fb1 的状态就如下面这张图这样:

下面贴上原书中委托链的状态图(偷个懒,太难画了),

下面给出一个我本地委托测试的小Demo

Driver.cs

using System;

namespace EventDemo.CarDemo {
public class Driver {
public string Name { get; set; } public void DriveCar () {
Console.WriteLine ($"I am driver:{Name}");
}
}
}

Passenger.cs

namespace EventDemo.CarDemo {
public class Passenger { public string Name { get; set; } public void BoardCar () {
System.Console.WriteLine ($"我上车了,我是:{Name}");
}
}
}

MyCar.cs

using System;

namespace EventDemo.CarDemo {

    public delegate void CarHandler ();

    public class MyCar {
//定义一个 上车 委托方法的事件
public event CarHandler CarNumberNotification; public void RunCar () {
Console.WriteLine ($"好的,准备上车了!");
if (CarNumberNotification != null) {
CarNumberNotification ();
}
}
}
}

Program.cs

 //纯委托版本
CarHandler carHandler = null;
carHandler += driver.DriveCar;
carHandler += passenger.BoardCar;
carHandler.Invoke();

深入理解委托(Delegate)的更多相关文章

  1. 理解委托(delegate)及为什么要使用委托

    理解委托(delegate)及为什么要使用委托 委托:是一种定义方法签名的类型. 当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联. 您可以通过委托实例调用方法. 上述为官方说法,理解起来 ...

  2. [.NET] C# 知识回顾 - 委托 delegate (续)

    C# 知识回顾 - 委托 delegate (续) [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6046171.html 序 上篇<C# 知识回 ...

  3. C# 委托Delegate(一) 基础介绍&用法

    本文是根据书本&网络 前人总结的. 1. 前言 定义&介绍: 委托Delegate是一个类,定义了方法的类型, 使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态地赋给参数的 ...

  4. C#基础知识六之委托(delegate、Action、Func、predicate)

    1. 什么是委托 官方解释 委托是定义方法签名的类型,当实例化委托时,您可以将其实例化与任何具有兼容签名的方法想关联,可以通过委托实例调用方法. 个人理解 委托通俗一点说就是把一件事情交给别人来帮助完 ...

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

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

  6. c# 委托 delegate

    委托是一种存储函数引用的类型,在事件和事件的处理时有重要的用途 通俗的说,委托是一个可以引用方法的类型,当创建一个委托,也就创建一个引用方法的变量,进而就可以调用那个方法,即委托可以调用它所指的方法. ...

  7. Unity 项目中委托Delegate用法案例

    Unity中Delegate的用法场景 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar - ...

  8. 关于C# 委托(delegate)与事件(event)的用法及事例

    C#中的委托和事件对于新手可能会有一点难理解,所以先从一个小例子入手,以便能更好的理解其如何使用.有一个学生每天定闹钟在早上6点起床,所以当每天早上6点的时候,闹钟就会响起来,从而学生才会按时起床. ...

  9. 编写高质量代码改善C#程序的157个建议——建议44:理解委托中的协变

    建议44:理解委托中的协变 委托中的泛型变量天然是部分支持协变的.为什么是“部分支持协变”?看下面示例: class Program { public delegate T GetEmployeeHa ...

随机推荐

  1. JS常见操作,日期操作,字符串操作,表单验证等

    复制代码 //第一篇博文,希望大家多多支持 /***** BasePage.js 公共的 脚本文件 部分方法需引用jquery库 *****/ //#region 日期操作 //字符串转化为时间. f ...

  2. Django--基本篇:项目结构与设计模式(MVC)

    Django在项目开发中有着结构清晰.层次明显.容易编写理解查阅demo的优点,那么我们来个小案例具体看看.    一.项目结构简析: 我们按照上一篇中的开发流程步骤创建一个新项目myblog,项目下 ...

  3. SpagoBi开发示例——员工离职人数统计

    1.开发工具:SpagoBIStudio_5.1,操作界面和使用方法和eclipse没差 安装参考:http://www.cnblogs.com/starlet/p/4778334.html   2. ...

  4. 听翁恺老师mooc笔记(2)-第一个程序--&运算符

    使用devC++写hello world 第一步:文件-新建-源代码.然后输入"输出hello world"程序: 注意:写程序时关闭中文输入法,否则语句输入的分号可能会被识别为错 ...

  5. Beta冲刺第三天

    一.昨天的困难 没有困难. 二.今天进度 1.林洋洋:修改权限相关的资源表示,修复flex布局排版高度问题,修复文件更新问题,去除登录页面的默认账号密码,服务器部署. 2.黄腾达:修复日程首次执行时间 ...

  6. Linux学习--线程概念

    线程 我们知道 ,进程在各自独立的地址空间中运行,进程之间共享数据需要用mmap或者进程间通信机制,本节我们学习如何在一个进程的地址空间中执行多个线程.有些情况需要在一个进程中同时执行多个控制流程,这 ...

  7. iOS开发-继承特征详解

    面向对象的三大特性:封装,继承,多态. 1.继承 继承既能保证类的完整,又能简化代码. 把公共的方法和实例变量写在子类,子类只需要写父类独有的实例变量和方法即可. 继承是面向对象三大特性之一,合理的继 ...

  8. Struts2之配置文件中Action的详细配置

    在Struts2之配置一文中,我们知道一个struts配置文件可以分为三部分:常量配置    包含其他配置文件的配置    Action配置  . 这其中 常量配置  和 包含其他配置文件的配置  二 ...

  9. ThreadLocal就是这么简单

    前言 今天要研究的是ThreadLocal,这个我在一年前学习JavaWeb基础的时候接触过一次,当时在baidu搜出来的第一篇博文ThreadLocal,在评论下很多开发者认为那博主理解错误,给出了 ...

  10. PostgreSQL 客户端乱码问题

    关于客户端和服务器端的乱码问题, POSTGRESQL字符集问题总结 总结的很详细, 特别棒. 这里让我头痛了很久的问题在于 终端 上字符编码的问题, 由于我的mbp上的 iterm2 的默认编码为 ...