深入理解委托(Delegate)
前言
委托其实一直以来都感觉自己应该挺熟悉的,直到最近又去翻了翻 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 中三个重要的非公共字段
- _target 类型为 System.Object ,当委托队形包装一个静态方法时,这个字段为null。当委托对象包装一个实例方法时,这个字段引用的是回调方法要操作的对象。换言之,这个字段指出要传给实例方法的隐式参数this 的值。
- _methodPtr 类型为 System.IntPtr 一个内部的整数值,CLR 用它来标识要回调的方法
- _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)的更多相关文章
- 理解委托(delegate)及为什么要使用委托
理解委托(delegate)及为什么要使用委托 委托:是一种定义方法签名的类型. 当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联. 您可以通过委托实例调用方法. 上述为官方说法,理解起来 ...
- [.NET] C# 知识回顾 - 委托 delegate (续)
C# 知识回顾 - 委托 delegate (续) [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6046171.html 序 上篇<C# 知识回 ...
- C# 委托Delegate(一) 基础介绍&用法
本文是根据书本&网络 前人总结的. 1. 前言 定义&介绍: 委托Delegate是一个类,定义了方法的类型, 使得可以将方法当做另一个方法的参数来进行传递,这种将方法动态地赋给参数的 ...
- C#基础知识六之委托(delegate、Action、Func、predicate)
1. 什么是委托 官方解释 委托是定义方法签名的类型,当实例化委托时,您可以将其实例化与任何具有兼容签名的方法想关联,可以通过委托实例调用方法. 个人理解 委托通俗一点说就是把一件事情交给别人来帮助完 ...
- C#学习之初步理解委托、事件、匿名方法和Lambda
最经在学习LinqtoSql,然后扯到Lambda表达式,然后扯到匿名方法,然后扯到委托,最后扯到事件处理...后来发现对委托这个概念和事件处理这个过程理解得不是很清晰,遂得一下学习笔记.那里说得不对 ...
- c# 委托 delegate
委托是一种存储函数引用的类型,在事件和事件的处理时有重要的用途 通俗的说,委托是一个可以引用方法的类型,当创建一个委托,也就创建一个引用方法的变量,进而就可以调用那个方法,即委托可以调用它所指的方法. ...
- Unity 项目中委托Delegate用法案例
Unity中Delegate的用法场景 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar - ...
- 关于C# 委托(delegate)与事件(event)的用法及事例
C#中的委托和事件对于新手可能会有一点难理解,所以先从一个小例子入手,以便能更好的理解其如何使用.有一个学生每天定闹钟在早上6点起床,所以当每天早上6点的时候,闹钟就会响起来,从而学生才会按时起床. ...
- 编写高质量代码改善C#程序的157个建议——建议44:理解委托中的协变
建议44:理解委托中的协变 委托中的泛型变量天然是部分支持协变的.为什么是“部分支持协变”?看下面示例: class Program { public delegate T GetEmployeeHa ...
随机推荐
- Beta 第六天
今天遇到的困难: github服务器响应很慢 推图的API接口相应较慢,超过了初始设定的最大延迟时间,导致了无法正确返回图片 ListView滑动删除Demo出现了某些Bug,这些Bug可能导致了某些 ...
- C语言-学生博客汇总
一.学生个人博客汇总 五班 学号 姓名 博客地址 4079 马天琦 http://www.cnblogs.com/simalang/ 4080 马宇欣 http://www.cnblogs.com/m ...
- 利用python实现简单邮件功能
#!/usr/bin/env python # -*- coding:utf-8 -*- import smtplib from email.utils import formataddr from ...
- logging日志
import logging logging.basicConfig(filename='log.log', format='%(asctime)s - %(name)s - %(levelname) ...
- Flask 测试
测试是每个应用系统发布前必须经历的步骤,自动化测试对测试效率的提高也是毋庸置疑的.对于Flask应用来说,当然可以使用Web自动化测试工具,比如Selenium等来测.Flask官方推荐的自动化测试方 ...
- MySql使用存储过程实现事务的提交或者回滚
DELIMITER $$ DROP PROCEDURE IF EXISTS test_sp1 $$ CREATE PROCEDURE test_sp1( ) BEGIN ; ; START TRANS ...
- Binary Tree Xorder Traversal
 * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeN ...
- Spring邮件发送1
注意:邮件发送code中,邮件服务器的申请和配置是比较主要的一个环节,博主这里用的是QQ的邮件服务器.有需要的可以谷歌.百度查下如何开通. 今天看了下Spring的官方文档的邮件发送这一章节.在这里记 ...
- api-gateway实践(04)新服务网关 - 新手入门
一.网关引擎环境 1.下载代码 2.搭建环境 3.打包部署 二.配置中心环境 1.下载代码 2.搭建环境 3.打包部署 三.创建业务实例 1.以租户身份登录配置中心,注册 group.version. ...
- Web开发笔记
jquery ui draggable clone之后不会克隆draggable功能,要重新设置