【问题标题】:Using delegates in C#在 C# 中使用委托
【发布时间】:2017-11-19 13:51:32
【问题描述】:

在 C# 语言和 .NET 框架中,您能帮助我理解委托吗? 我试图检查一些代码,发现我收到的结果出乎我的意料。这里是:

class Program
{
    public static int I = 0;

    static Func<string> del = new Func<string>(I.ToString);

    static void Main(string[] args)
    {
        I = 10;
        Console.WriteLine("{0}", del());
    }
}

答案是 0,但不是 10。为什么?

【问题讨论】:

  • @Rotem:不,他没有。
  • @Rotem - 这是一个委托声明。添加() 将调用ToString
  • 抱歉,没用过Funcs,只是猜测:)
  • +1 提出了一个很好的问题,问得很好。一个看似简单的问题如何突出语言/平台中难以理解的领域的绝佳示例。
  • 一个(单播)委托实例可以指向实例方法或static 方法。当它表示一个实例方法时,委托持有 both 调用该方法的“目标”对象和方法信息。所以当你说del = I.ToString; 时,del 将持有对象I,这里是Int32(不可变值类型)。当您使用匿名函数del = () =&gt; I.ToString(); 时,编译器会创建一个方法static string xxx() { return I.ToString(); },而del 对象会保存该生成的方法。

标签: c# .net delegates


【解决方案1】:

原因如下:

您声明委托的方式直接指向静态 int 实例的ToString 方法。它是在创建时捕获的。

正如 flindeberg 在下面的 cmets 中指出的,每个委托都有一个目标和一个要在目标上执行的方法。

在这种情况下,要执行的方法显然是ToString方法。有趣的部分是执行该方法的实例:它是创建时I 的实例,这意味着委托没有使用I 来获取要使用的实例,但它存储了对实例本身。

稍后您将I 更改为不同的值,基本上是为其分配一个新实例。这不会神奇地更改您的委托中捕获的实例,为什么要这样做?

要获得您期望的结果,您需要将委托更改为:

static Func<string> del = new Func<string>(() => I.ToString());

像这样,委托指向一个匿名方法,该方法在委托执行时在当前I 上执行ToString

在这种情况下,要执行的方法是在声明委托的类中创建的匿名方法。实例为空,因为它是静态方法。

查看编译器为第二版委托生成的代码:

private static Func<string> del = new Func<string>(UserQuery.<.cctor>b__0);
private static string cctor>b__0()
{
    return UserQuery.I.ToString();
}

如您所见,这是一种正常的操作方法。在我们的例子中,它返回在 I 的当前实例上调用 ToString 的结果。

【讨论】:

  • @flindeberg:你甚至可以使用自己的类来代替 int。它的行为仍然相同,因为根本原因没有改变:委托指向一个特定对象上 ToString 的特定化身。这是引用类型还是值类型都没有关系。
  • @user1859587:委托有一个方法和一个目标(实例),它是否捕获您的实例或 lambda 函数的实例又包含对该实例的引用很重要。
  • @user1859587:不客气。顺便说一句:我试图更新答案以使其更清楚这里发生了什么。您可能想重新阅读它:-)
  • Daniel,只是为了确认 flindeberg 的评论:您的答案是正确的,但您关于拳击的 cmets 不是。 user1859587 是正确的:观察到的行为是委托捕获呼叫接收者这一事实的结果。尽管在 int 上调用 ToString 的接收者是对 int 变量的引用,但委托无法将对 int 变量的引用放在堆上;对变量的引用只能进入临时存储。所以它做了下一件最好的事情:它将 int 装箱并引用该堆位置。
  • 接收器被装箱这一事实的一个有趣结果是,不可能在可为空的 int 上创建 GetValueOrDefault() 的委托,因为装箱可为空的 int 会产生装箱的 int,而不是装箱的 int可以为空的 int,而装箱的 int 没有 GetValueOrDefault() 方法。
【解决方案2】:

您需要将I 传递给您的函数,以便I.ToString() 可以在适当的时间执行(而不是在创建函数时)。

class Program
{
    public static int I = 0;

    static Func<int, string> del = num => num.ToString();

    static void Main(string[] args)
    {
        I = 10;
        Console.WriteLine("{0}", del(I));
    }
}

【讨论】:

    【解决方案3】:

    这是应该怎么做的:

    using System;
    
    namespace ConsoleApplication1
    {
    
        class Program
        {
            public static int I = 0;
    
            static Func<string> del = new Func<string>(() => {
                return I.ToString();
            });
    
            static void Main(string[] args)
            {
                I = 10;
                Console.WriteLine("{0}", del());
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      C#委托启用封装对象和实例以及方法。委托声明定义派生自 System.Delegate 类的类。委托实例封装了一个调用列表,该列表是一个或多个方法的列表,每个方法称为可调用实体。

      了解更多表格

      http://asp-net-by-parijat.blogspot.in/2015/08/what-is-delegates-in-c-how-to-declare.html

      【讨论】:

        【解决方案5】:

        我的猜测是因为 int 是通过值而不是引用传递的,因此在创建委托时,它是“I”(0) 的当前值的方法 ToString 的委托。

        【讨论】:

        • 您的猜测不正确。这与值类型与引用类型无关。引用类型也会发生完全相同的事情。
        • 其实是这样,例如我们使用一个类实例,而 ToString 正在操作实例数据以获得返回值,它会产生当前类状态的返回值,而不是类的状态委托已创建。创建委托并且该类只有一个实例时,该函数不会运行。
        • Func(() => I.ToString()) 应该也能正常工作,因为在方法调用之前我们不使用“I”。
        • 但这并不等同于这里发生的事情。如果您使用类 Foo 而不是 int 并将行 I = 10 更改为 I = new Foo(10) 您将得到与当前代码完全相同的结果。 I.Value = 10 完全是另外一回事。这不会将新实例分配给 I。但是为I 分配一个新实例是这里的重点。
        • 好的,所以问题是重新分配“I”,如果“I”是可变的,并且我们在不重新分配 I 的情况下更改对象,它会起作用。在这个例子中,我们不能这样做,因为 i 是 int(不可变)。
        猜你喜欢
        • 2019-10-07
        • 1970-01-01
        • 2012-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-03
        • 1970-01-01
        相关资源
        最近更新 更多