首页 > 编程语言 >C# 委托和闭包

C# 委托和闭包

时间:2023-05-26 12:34:56浏览次数:44  
标签:闭包 int32 Console 委托 C# System IL Foo method

委托是什么

大部分的解释是 委托是一个对方法的引用,可以不用自己执行,而是转交给其他对象。就好比每天都有一个黄毛旅行者,给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);

可能我们还见过其他写法,比如 Func和 Action,他们的区别在下面的代码里面有所展示,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/yinghualuowu/p/17011510.html

标签:闭包,int32,Console,委托,C#,System,IL,Foo,method
From: https://www.cnblogs.com/wugh8726254/p/17434417.html

相关文章

  • .env.development(开发环境)、.env.prodction(正式环境)、自定义环境 例如:读取vue项目根
    .env.development(开发环境)、.env.prodction(正式环境)、自定义环境原文链接:https://blog.csdn.net/qq_42855675/article/details/114261585文章目录1.配置文件:2.命名规则3.关于文件的加载使用自定义环境1.配置文件:      .env.development:开发环境下的配置文件 ......
  • FLEX实践:主应用程序、MODULE、COMPONENT组合
    本次实践主要是记录下如何在FLEX主应用程序中调用一个MODULE,而在MODULE中调用COMPONENT。在开始之前先来简单谈谈MODULE这个概念 --========================================================================模块(Module)开发的优点自不待说。FlexProject中建立多个Application......
  • windows WSL安装hedgedoc
    GitWSL似乎默认装好了git,用下面命令查一下可以查到root@DESKTOP-FTUL9EC:~#git--versiongitversion2.25.1Docker17.03.1orhigher看了官方文档,安装dockerengine需要如下ubuntu版本:UbuntuLunar23.04UbuntuKinetic22.10UbuntuJammy22.04(LTS)UbuntuF......
  • 关于AWS中VPC下的IGW-internet gateway的创建与说明
    关于AWS中VPC下有一个资源叫做 Internetgateways,也就是我们常说的IGW关于IGW,我们可以参考官网文档 AmazonVPC/ UserGuide中有如下说明:Aninternetgatewayisahorizontallyscaled,redundant,andhighlyavailableVPCcomponentthatallowscommunicationbetw......
  • Flex实践——Personal Card制作
    label,text,textInput,button,radioButton,comboBox .     准备工作和上几次描述的一样,新建一个MXMLApplication:PersonalCard      然后拖控件,具体步骤不在这里描述了,都是直接拖控件就能用了,注意给需要操作的控件标上ID     接下来,来看一下comboBox的具......
  • OnlineDictionary
        WhatshouldItalkaboutit?It'sjustasimplecompetition ,theyjustwanttoseewhatcanwedo,howgoodideascanwethinkup.........    IjustwanttoexpressmyownthoughtstoshowwhatamIthinkingabout.Ijustwanttocoopratewith......
  • 外汇天眼:Bee Capital──诱导投资慈善私募计划,威胁冻结账户诓骗缴费!
    近年来愈来愈多人意识到投资的重要性,明白如果只靠薪水收入,基本上无法实现累积大量财富、达到财务自由的目标,并希望通过积极寻找各种投资机会,建立稳定、丰盛的财务状况。然而,市面上的交易商良莠不齐,甚至有许多诈骗集团设立的黑平台,伺机骗取民众的资金。日前,一位受害者向外汇天眼爆......
  • C# 反射的定义和应用场景
     1什么是反射首先要复习一下C#的编译过程,可以解释为下图其中dll/exe中,包括元数据(metadata)和IL(中间语言IntermediateLanguage)另外还出现的其他名词:CLR(公共语言运行时,CommonLanguageRuntime)和JIT(实时编译器JustinTime)总结:一个运行的程序查看本身的元数据或......
  • 用pycharm创建一个django框架
    用pycharm创建一个django框架注意解释器的选择和文件路径创建完django项目1.自动创建了一个templates目录(先删除)2.把settings里的TEMPLATES=[{'BACKEND':'django.template.backends.django.DjangoTemplates','DIRS':[BASE_DIR/'templates......
  • Vue3.3 的新功能的体验(下):泛型组件(Generic Component) 与 defineSlots
    上一篇说了DefineOptions、defineModel、Props的响应式解构和从外部导入类型这几个新功能,但是没有说Generic、defineSlots等,这是因为还没有完全搞清楚可以用在什么地方。折腾了几天终于弄清楚了。这还要从TS的泛型说起。泛型的目的和意义泛型仅仅只是表达传啥都行吗?当然......