dotnet 委托的实现解析(2)开放委托和封闭委托 (Open Delegates vs. Closed Delegates)
前言
这是个人对委托的理解系列第二篇,部分翻译自 Open Delegates vs. Closed Delegates – SLaks.Blog,好像还没人翻译过,加上部分个人理解。希望能对大家理解委托有所帮助。
正文
.Net支持两种委托:开放委托和封闭委托。open delegates
和 closed delegates
译者注:这里不是作者这么分的,确实写在dotnet的官方文档和注释里。当然翻译的名称值得考量。
封闭委托:
当你创建一个指向实例对象方法的委托时,这个实例对象就被保存在委托的Target
属性中,这个属性(也就是包含方法的实例对象)被当作方法的第一个参数传给委托指向的方法。对于实例对象的方法来说,就是this
,对于静态方法来说,就是方法的第一个参数。这样的委托被称为封闭委托(closed delegates)因为他们以js闭包的方式把静态方法的第一个参数或者实例方法的this
封进了委托实例之中。
译者注: 这里说的很抽象,意思就是封闭委托会把某些参数作为
Target
属性带进委托中。后面有例子说明。
开放委托
我们同样也能创建一个不隐含传第一个参数的开式委托,这种委托不使用Target
属性,相反,所有委托目标方法的参数都是按委托的形参列表实际传递的,包括第一个参数。因此,一个指向给定方法的开放委托一定比指向同一个方法的封闭委托多传一个参数(也就是this
或者第一个参数)。开放委托一般用于指向静态方法(同理:封闭委托一般用于实例方法)。当你创建一个指向静态方法的委托时,你一般不想这个委托持有这个方法的第一个参数。
译者注:原文比较抽象,结合后面的例子比较容易理解。
简短说明
除了上面说的这两种通用的情况来说(也就是静态方法创建开放委托,实例方法创建封闭委托),在.Net 2.0和之后我们还能创建指向实例方法的开放委托和指向静态方法的封闭委托。但是这样C#就没有相应的语法糖支持,只能通过Delegate.CreateDelegate
方法来创建。
区分这两种委托主要看
Target
属性,原文的例子不太有些不太容易理解。这里结合自己的理解。重新写了例子,读完本文的例子可以再去看原文的例子就比较容易理解了。
一般情况的例子
一般情况的封闭委托(实例方法)
internal class HelloWorld
{
public void HelloWorldInstance() => Console.WriteLine("hello world");
public delegate void SayHi();
public void Main()
{
var helloWorldInstance = new SayHi(HelloWorldInstance);
}
}
这里实际调用的System.Private.CoreLib
中的Delegate.CoreCLR.cs
代码为:
[System.Diagnostics.DebuggerNonUserCode]
private void CtorClosed(object target, IntPtr methodPtr)
{
if (target == null)
ThrowNullThisInDelegateToInstance();
this._target = target;
this._methodPtr = methodPtr;
}
看名字就很容易理解,注意这里对Target
的赋值且一定不为null
。
一般情况的开放委托(静态方法)
internal class HelloWorld
{
public static void HelloWorldStatic(string msg) => Console.WriteLine(msg);
public delegate void SayHiWithParam(string msg);
public void Main()
{
var helloWorld3 = new SayHiWithParam(HelloWorldStatic);
helloWorld3("hello world");
}
}
这里实际调用的System.Private.CoreLib
中的MulticastDelegate.cs
代码为:
[System.Diagnostics.DebuggerNonUserCode]
private void CtorOpened(object target, IntPtr methodPtr, IntPtr shuffleThunk)
{
this._target = this;
this._methodPtr = shuffleThunk;
this._methodPtrAux = methodPtr;
}
注意:
- 这里的
target
实际为null
,所以_target
的赋值对象是this
,而创建出的委托的Target
属性为null
。 - 这里
_methodPtr
的赋值对象是shuffleThunk
。
特殊情况的例子
前面说了,不一般情况下我们需要使用通过Delegate.CreateDelegate
方法来创建委托。
特殊情况的封闭委托(静态方法)
internal class HelloWorld
{
public static void HelloWorldStatic(string msg) => Console.WriteLine(msg);
public void Main()
{
var closed = Delegate.CreateDelegate(typeof(Action), "a", typeof(HelloWorld).GetMethod(nameof(HelloWorld.HelloWorldStatic)));
closed.DynamicInvoke();
}
}
这里实际调用的System.Private.CoreLib
中的Delegate.cs
代码为:
// V2 api: Creates open or closed delegates to static or instance methods - relaxed signature checking allowed.
public static Delegate CreateDelegate(Type type, object? firstArgument, MethodInfo method) => CreateDelegate(type, firstArgument, method, throwOnBindFailure: true)!;
特别需要注意:
- 这里创建的委托类型为:
typeof(Action)
不是typeof(Action<string>)
因为,"a"
做为第一个参数已经是委托的Target
属性了。
特殊情况开放委托(实例方法)
public class HelloWorld
{
public void HelloWorldInstance() => Console.WriteLine("hello world");
public void Main()
{
var open = Delegate.CreateDelegate(typeof(Action<HelloWorld>), typeof(HelloWorld).GetMethod(nameof(HelloWorld.HelloWorldInstance)));
open.DynamicInvoke(this);
}
}
同样特别需要注意:
- 这里创建的委托类型为:
typeof(HelloWorld)
不是typeof(Action<string>)
- 可以注意到委托的
Target
属性为null
。
原文的例子较难一点,感兴趣的可以通过前文链接查看。接着我们回到原文。
原文中的其他注意点
原文中提供了其他部分需要注意的部分包括
- 不能使用值类型作为第一个参数创建静态方法的封闭委托。参考链接c# - Extension methods defined on value types cannot be used to create delegates - Why not? - Stack Overflow
- C# 3对创建封闭委托提供了部分语法支持,你可以从扩展方法创建委托,就好像它是它所扩展的类型的实例方法一样 例如:
var allNumbers = Enumerable.Range(1, Int32.MaxValue);
Func<int, IEnumerable<int>> countTo = allNumbers.Take;
countTo
这里可以作为IEnumerable<int>
的实例方法来使用。
- 作者最后说,除了扩展方法,特殊情况的委托的应用只占很小一部分,但对我们对委托的理解很重要。
me:不明觉厉。以及以上是这个系列的第二篇,有机会希望带大家更深入的理解委托。
dotnet 委托的实现解析(2)开放委托和封闭委托 (Open Delegates vs. Closed Delegates)的更多相关文章
- 【详细】【转】C#中理解委托和事件 事件的本质其实就是委托 RabbitMQ英汉互翼(一),RabbitMQ, RabbitMQ教程, RabbitMQ入门
[详细][转]C#中理解委托和事件 文章是很基础,但很实用,看了这篇文章,让我一下回到了2016年刚刚学委托的时候,故转之! 1.委托 委托类似于C++中的函数指针(一个指向内存位置的指针).委托 ...
- 用五分钟重温委托,匿名方法,Lambda,泛型委托,表达式树
这些对老一代的程序员都是老生常谈的东西,没什么新意,对新生代的程序员却充满着魅力.曾经新生代,好多都经过漫长的学习,理解,实践才能掌握委托,表达式树这些应用.今天我尝试用简单的方法叙述一下,让大家在五 ...
- 转帖:用五分钟重温委托,匿名方法,Lambda,泛型委托,表达式树
用五分钟重温委托,匿名方法,Lambda,泛型委托,表达式树 这些对老一代的程序员都是老生常谈的东西,没什么新意,对新生代的程序员却充满着魅力.曾经新生代,好多都经过漫长的学习,理解,实践才能掌握委托 ...
- C#中使用委托、接口、匿名方法、泛型委托实现加减乘除算法
使用C#实现加减乘除算法经常被用作新手练习.本篇来分别体验通过委托.接口.匿名方法.泛型委托来实现. 使用委托实现 加减乘除拥有相同的参数个数.类型和返回类型,首先想到了使用委托实现. //创建一个委 ...
- 委托,匿名方法,Lambda,泛型委托,表达式树
一.委托:完成一个委托应分三个步骤://step01:首先用delegate定义一个委托;public delegate int CalculatorAdd(int x, int y);//step0 ...
- 委托、Lambda表达式、事件系列04,委托链是怎样形成的, 多播委托, 调用委托链方法,委托链异常处理
委托是多播委托,我们可以通过"+="把多个方法赋给委托变量,这样就形成了一个委托链.本篇的话题包括:委托链是怎样形成的,如何调用委托链方法,以及委托链异常处理. □ 调用返回类型为 ...
- 委托、Lambda表达式、事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性
委托是一个类. namespace ConsoleApplication1 { internal delegate void MyDelegate(int val); class Program { ...
- c#打包文件解压缩 C#中使用委托、接口、匿名方法、泛型委托实现加减乘除算法 一个简单例子理解C#的协变和逆变 对于过长字符串的大小比对
首先要引用一下类库:using Ionic.Zip;这个类库可以到网上下载. 下面对类库使用的封装方法: 得到指定的输入流的ZIP压缩流对象 /// <summary> /// 得到指定的 ...
- C#委托,匿名方法,Lambda,泛型委托,表达式树代码示例
第一分钟:委托 有些教材,博客说到委托都会提到事件,虽然事件是委托的一个实例,但是为了理解起来更简单,今天只谈委托不谈事件.先上一段代码: 下边的代码,完成了一个委托应用的演示.一个委托分三个步骤: ...
随机推荐
- maven转web项目时 在Project Facets下转换时 Further configuration available没有出现
如果下边的 further configuration available不出来 把Dynamic web module 去掉勾选,应用与项目,然后再点开项目的properties,再选中Dynami ...
- rpm与yum安装及管理程序
安装及管理程序 1.Linux应用程序基础 2.RPM软件包管理工具 3.yum源仓库创建 1.应用程序与系统命令的关系如图: 典型应用程序的目录结构如图: 常见的软件包封装类型如图: 2.RPM包 ...
- nodejs的安装及创建项目
安装windows nodejs教程:1.官网下载windows安装:2.CMD中输入:npm -g install koa -generator 创建项目:1.首先新建文件夹2.CMD中输入CD 文 ...
- go基础——输入输出
package main import ( "bufio" "fmt" "os" ) func main() { /* 输入和输出 fmt包 ...
- springcloud+gateway微服务整合swagger
单一的微服务集成swagger: maven: <dependency> <groupId>io.springfox</groupId> <artifactI ...
- haproxy https实现
一.实验环境 一.准备后端服务器 # yum -y install nginx # echo "10.0.0.7" > /usr/share/nginx/html/index ...
- Hyperledger Fabric 2.x 自定义智能合约
一.说明 为了持续地进行信息的更新,以及对账本进行管理(写入交易,进行查询等),区块链网络引入了智能合约来实现对账本的访问和控制:智能合约在 Fabric 中称之为 链码,是区块链应用的业务逻辑. 本 ...
- 一次Kafka内存泄露排查经过
一.现象 服务部署后内存总体呈上升趋势 二.排查过程 通过go tool pprof收集了三天内存数据 2月11号数据: 2月14号数据: 2月15号数据: 可以看到newPartitionProdu ...
- Spring Boot部署之jar包运行
上篇阐述了Spring Boot war部署项目,本篇阐述另一种运行方式:jar包运行. 一.打jar包 1.修改pom.xml配置 2.执行package(对于module执行package之前需要 ...
- ios plist获取权限
最近太忙了,没有时间写vue 这个权限获取有点坑,极不好记,所以备份一份 <key>NSVideoSubscriberAccountUsageDescription</key> ...