前言

本文只是为了复习,巩固,和方便查阅,一些知识点的详细知识会通过相关链接和文献标记出来。

委托是什么

大部分的解释是 委托是一个对方法的引用,可以不用自己执行,而是转交给其他对象。就好比每天都有一个黄毛旅行者,给npc做委托任务一样,npc并不是自己去做任务。

于是我们可以有以下代码,delegate就是声明一个委托,它的作用是调用sum方法

// See https://aka.ms/new-console-template for more information

using System.Diagnostics.CodeAnalysis;
using System.Threading.Channels; Foo foo = sum;
Console.Write(foo(1, 2)); static int sum(int a, int b) => a + b; static int minus(int a, int b) => a - b; internal delegate int Foo(int a, int b);

可能我们还见过其他写法,比如 FuncAction,他们的区别在下面的代码里面有所展示,Func存在返回值,Actionvoid

Func<int, int, int> func = Sum;

Action ac = OutPut;

func.Invoke(1, 2);

static void OutPut()
{
Console.WriteLine("action");
}

如果反编译其中的实现,一眼顶针~

namespace System
{
/// <summary>Encapsulates a method that has two parameters and returns a value of the type specified by the <typeparamref name="TResult" /> parameter.</summary>
/// <param name="arg1">The first parameter of the method that this delegate encapsulates.</param>
/// <param name="arg2">The second parameter of the method that this delegate encapsulates.</param>
/// <typeparam name="T1">The type of the first parameter of the method that this delegate encapsulates.</typeparam>
/// <typeparam name="T2">The type of the second parameter of the method that this delegate encapsulates.</typeparam>
/// <typeparam name="TResult">The type of the return value of the method that this delegate encapsulates.</typeparam>
/// <returns>The return value of the method that this delegate encapsulates.</returns>
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
}
public delegate void Action();

委托内部是什么

如果细心的话,会从 Func发现一点线索 Invoke,我们把最开始的代码变为IL看看,嗯,也是存在 Foo::Invoke

  // [7 1 - 7 26]
IL_000d: ldloc.0 // foo
IL_000e: ldc.i4.1
IL_000f: ldc.i4.2
IL_0010: callvirt instance int32 Foo::Invoke(int32, int32)
IL_0015: call void [System.Console]System.Console::Write(int32)
IL_001a: nop
IL_001b: nop
IL_001c: nop
IL_001d: ret

Foo在完整的展示出来,可以知道,它是由MulticastDelegate继承得到,以及存在三个方法 Invoke BeginInvoke EndInvoke,把原来的方法放在了object中,再Foo::Invoke

有关MulticastDelegate(多播委托)可以阅读 https://learn.microsoft.com/zh-cn/dotnet/api/system.multicastdelegate?view=net-7.0

.class private sealed auto ansi
Foo
extends [System.Runtime]System.MulticastDelegate
{ .method public hidebysig specialname rtspecialname instance void
.ctor(
object 'object',
native int 'method'
) runtime managed
{
// Can't find a body
} // end of method Foo::.ctor .method public hidebysig virtual newslot instance int32
Invoke(
int32 a,
int32 b
) runtime managed
{
// Can't find a body
} // end of method Foo::Invoke .method public hidebysig virtual newslot instance class [System.Runtime]System.IAsyncResult
BeginInvoke(
int32 a,
int32 b,
class [System.Runtime]System.AsyncCallback callback,
object 'object'
) runtime managed
{
// Can't find a body
} // end of method Foo::BeginInvoke .method public hidebysig virtual newslot instance int32
EndInvoke(
class [System.Runtime]System.IAsyncResult result
) runtime managed
{
// Can't find a body
} // end of method Foo::EndInvoke
} // end of class Foo

委托的多个调用和不变性

就好比旅行者可以接到4个委托一下,这里Foo也可以一次性接到多个方法,要不然为啥叫多播委托呢

Foo foo = Sum;
foo += Minus;
Console.Write(foo(1, 2)); //输出-1

应该好理解,执行最后一个方法。如果想要一个个执行,可以这么做 foo.GetInvocationList()

现在我们来看看这个不变性,顾名思义就是委托创建后是始终不变的。来一段代码,现在我们知道了委托也是个引用,那么假如我在re移除一个方法,会不会影响到foo?答案是不会。证明完毕~

Foo foo = Sum;
foo += Minus;
Foo re = foo;
re -= Minus; foreach (var del in foo.GetInvocationList())
{
Console.WriteLine(del.Method);
}

闭包

可以理解为 一个代码块(在C#中,指的是匿名方法或者Lambda表达式,也就是匿名函数),并且这个代码块使用到了代码块以外的变量,于是这个代码块和用到的代码块以外的变量(上下文)被“封闭地包在一起”

这下看懂了(

以下代码就存在一个变量i,那为啥会输入全是5呢?

public static class Test
{
public static void Foo()
{
List<Action> ac = new List<Action>(); for (int i = 0; i < 5; i++)
{
ac.Add(() =>
{
Console.WriteLine(i);
});
} foreach (var action in ac)
{
action.Invoke();
}
}
}
5
5
5
5
5

把代码翻译成IL,可以得到有效线索,首先 ac.Add(() => { Console.WriteLine(i);});被生成了一个类 c__DisplayClass0_0,存在字段 .field public int32 i,那么本来应该是处于循环的i被c__DisplayClass0_0赋予了更长的生命周期,加上for循环看做是同一个外部变量,导致了这个问题的出现。

  .class nested private sealed auto ansi beforefieldinit
'<>c__DisplayClass0_0'
extends [System.Runtime]System.Object
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 ) .field public int32 i .method public hidebysig specialname rtspecialname instance void
.ctor() cil managed
{
.maxstack 8 IL_0000: ldarg.0 // this
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: nop
IL_0007: ret } // end of method '<>c__DisplayClass0_0'::.ctor .method assembly hidebysig instance void
'<Foo>b__0'() cil managed
{
.maxstack 8 // [18 17 - 18 18]
IL_0000: nop // [19 21 - 19 42]
IL_0001: ldarg.0 // this
IL_0002: ldfld int32 Delegate.Test/'<>c__DisplayClass0_0'::i
IL_0007: call void [System.Console]System.Console::WriteLine(int32)
IL_000c: nop // [20 17 - 20 18]
IL_000d: ret } // end of method '<>c__DisplayClass0_0'::'<Foo>b__0'
} // end of class '<>c__DisplayClass0_0'

怎么解决闭包问题

实际上vs已经很贴心的提示了,如果非得使用闭包,可以采取以下两种办法

for (int i = 0; i < 5; i++)
{
var i1 = i;
ac.Add(() => { Console.WriteLine(i1);});
}
List<int> li = new List<int> { 1, 2, 3, 4, 5 };

foreach (var value in li)
{
ac.Add(() => { Console.WriteLine(value); });
}

参考链接和文件代码

https://www.cnblogs.com/cdaniu/p/15352317.html

《C#8.0本质论》委托

《C#图解教程》委托

https://github.com/yinghualuowu/blogsCodeSimple/tree/main/Delegate

C# 委托和闭包的更多相关文章

  1. 委托、Lambda表达式、事件系列05,Action委托与闭包

    来看使用Action委托的一个实例: static void Main(string[] args) { int i = 0; Action a = () => i++; a(); a(); C ...

  2. C#的泛型委托与闭包函数

    前些天Wendy问我说Func<T, ResultT>是个什么意思,初学C#都觉得这样的写法很奇葩,甚至觉得这样写有点诡异,其实以我来看,这是体现C#函数式编程的又一个亮点. 从MSDN上 ...

  3. 浅谈.NET中闭包

    什么是闭包 闭包可以从而三个维度来说明.在编程语言领域,闭包是指由函数以及与函数相关的上下文环境组合而成的实体.通过闭包,函数与其上下文变量之间建立起关联关系,上下文变量的状态可以在函数的多次调用过程 ...

  4. 委托、Lambda表达式、事件系列07,使用EventHandler委托

    谈到事件注册,EventHandler是最常用的. EventHandler是一个委托,接收2个形参.sender是指事件的发起者,e代表事件参数. □ 使用EventHandler实现猜拳游戏 使用 ...

  5. 委托、Lambda表达式、事件系列06,使用Action实现观察者模式,体验委托和事件的区别

    在"实现观察者模式(Observer Pattern)的2种方式"中,曾经通过接口的方式.委托与事件的方式实现过观察者模式.本篇体验使用Action实现此模式,并从中体验委托与事件 ...

  6. 委托、Lambda表达式、事件系列04,委托链是怎样形成的, 多播委托, 调用委托链方法,委托链异常处理

    委托是多播委托,我们可以通过"+="把多个方法赋给委托变量,这样就形成了一个委托链.本篇的话题包括:委托链是怎样形成的,如何调用委托链方法,以及委托链异常处理. □ 调用返回类型为 ...

  7. 委托、Lambda表达式、事件系列03,从委托到Lamda表达式

    在"委托.Lambda表达式.事件系列02,什么时候该用委托"一文中,使用委托让代码简洁了不少. namespace ConsoleApplication2 { internal ...

  8. 委托、Lambda表达式、事件系列02,什么时候该用委托

    假设要找出整型集合中小于5的数. static void Main(string[] args) { IEnumerable<int> source = new List<int&g ...

  9. 委托、Lambda表达式、事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性

    委托是一个类. namespace ConsoleApplication1 { internal delegate void MyDelegate(int val); class Program { ...

  10. ASP.NET MVC Controller的激活

    最近抽空看了一下ASP.NET MVC的部分源码,顺带写篇文章做个笔记以便日后查看. 在UrlRoutingModule模块中,将请求处理程序映射到了MvcHandler中,因此,说起Controll ...

随机推荐

  1. Spring扩展——Aware接口

    Aware接口 在Spring中有许多的Aware接口,提供给应用开发者使用,通过Aware接口,我们可以通过set的方式拿到我们需要的bean对象(包括容器中提供的一些对象,ApplicationC ...

  2. 日志切面接口和方法demo,切面内重新抛出异常

    1. 定义切面 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { Stri ...

  3. Blender练习——SciFi枪械.md

    Blender练习--SciFi枪械 一.基本操作 常用快捷键 E 挤出 B 倒角,中途可通过滚轮或S来调整细分 Alt+点选 循环选择 Ctrl Alt+点选 并排选择 F 补面,比如一个碗口,将碗 ...

  4. 百度地图API 循环添加信息窗口问题

    百度地图API循环添加信息窗口,会出现所有消息只显示在第一个窗口的位置的问题.并且信息内容相同 解决方法1 转载自 https://blog.csdn.net/zz_mm/article/detail ...

  5. markdown语法支持测试

    latex 公式 \(v, w, \nu, \omega\) \[\iiint, \oiiint \] \(\Set{ x | x<\frac 1 2 }\) \(\displaystyle \ ...

  6. 将mysql的查询结果保存到文件中,并将文件从内网下载到外网服务器

    方法一: 直接执行命令: mysql> SELECT ordersid from yws.callback_clockd  ORDER BY ordersid ASC limit 0,10000 ...

  7. TypeScript 学习笔记 — 模板字符串和类型体操(十五)

    目录 基本介绍 字符串类型体操实操环节 1. 字符串首字母大写 CapitalizeString 2. 获取字符串第一个字符 FirstChar 3. 获取字符串最后一个字符 LastChar 4. ...

  8. [oeasy]python0129_unicode_中文字符序号_十三道大辙_字符编码解码_eval_火星文

    unicode 中文字符分类 回忆上次内容 字符集 从博多码 到 ascii 再到 iso-8859 系列 各自割据   如何把世界上各种字符统进行编码 unicode顺势而生不断进化 不过字符总量超 ...

  9. oeasy教您玩转vim - 68 - # 标签页tab

    ​ tab选项卡 回忆上次 上次有三种批量替换,分别是 :windo :bufdo :argdo 执行的{cmd}可以用|按顺序增加 分别对应的 windows 窗口 buffers 缓存文件 arg ...

  10. P10245 Swimming Pool题解

    P10245 Swimming Pool 题意 给你四条边 \(abcd\),求这四条边是否可以组成梯形. 思路 这显然是一道简单的普通数学题. 判断是否能构成梯形只需看四条边是否能满足,上底减下底的 ...