在C#中使用委托


79

在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。为什么?


12
@Rotem:不,他没有。
Daniel Hilgarth 2012年

3
@Rotem-这是一个委托声明。添加()将调用ToString
Oded

1
抱歉,从未使用过Funcs,是一个猜测:)
Rotem 2012年

2
+1是一个很好的问题,问得好。一个看似简单的问题如何突出显示语言/平台的一个尚未被很好理解的区域的很好的例子。
马丁

5
(单播)委托实例可以指向实例方法或static方法。当它代表实例方法时,委托包含要在其上调用方法的“目标”对象,又包含方法信息。因此,当您说时del = I.ToString;del将会保存I此处的对象Int32(不可变值类型)。当您使用匿名函数时del = () => I.ToString();,编译器将创建一个方法,static string xxx() { return I.ToString(); }并且该del对象将保存该生成的方法。
Jeppe Stig Nielsen

Answers:


79

原因如下:

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

正如flindeberg在下面的评论中指出的那样,每个代表都有一个目标和在该目标上执行的方法。

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

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

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

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

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

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

看一下编译器为委托的第二个版本生成的代码:

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

正如你所看到的,它是不正常的方法的东西。在我们的例子中,它返回调用ToString的当前实例的结果I


1
@flindeberg:您甚至可以使用自己的类而不是int。它的行为仍然相同,因为根本原因没有改变:委托指向一个特定对象上ToString的特定化身。这是引用类型还是值类型都没有关系。
Daniel Hilgarth 2012年

3
@ user1859587:委托有一个方法和一个目标(实例),它是捕获您的实例还是lambda函数的实例,进而包含对该实例的引用,这很重要。
flindeberg

1
@ user1859587:不客气。顺便说一句:我试图更新答案,以使这里的情况更加清楚。您可能需要重新阅读它:-)
Daniel Hilgarth 2012年

3
丹尼尔(Daniel),只是为了确认弗林德伯格(Flindeberg)的评论:您的回答是正确的,但关于拳击的评论却不正确。user1859587是正确的:观察到的行为是委托捕获呼叫的接收者这一事实的结果。尽管在int上调用ToString的接收者将是对int变量的引用,但是委托无法将对int变量的引用放在堆上。对变量的引用只能进入临时存储。因此,它做的第二件事是:将int装箱并引用该堆位置。
埃里克·利珀特

10
将接收方装箱的事实的一个有趣的结果是,不可能在可为空的int上委托GetValueOrDefault(),因为对可为空的int进行装箱会生成一个被装箱的int,而不是被装箱的可为空的int,并且被装箱的int具有没有GetValueOrDefault()方法。
埃里克·利珀特

4

您需要传入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));
    }
}

1

这是应该怎么做:

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());
        }
    }
}


-2

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


2
您的猜测是不正确的。这与值类型与引用类型无关。引用类型也会发生完全相同的事情。
Daniel Hilgarth 2012年

实际上是这样,例如,如果我们使用一个类实例,并且ToString在操作实例数据作为返回值,它将产生当前类状态的返回值,而不是创建委托时类的状态。创建委托并且类只有一个实例时,该函数不会运行。
Yshayy 2012年

Func <string>(()=> I.ToString())应该也可以正常工作,因为在方法调用之前我们不使用“ I”。
Yshayy 2012年

但这不等于这里发生的事情。如果您使用该类Foo代替int并将行更改I = 10I = new Foo(10)您,则将获得与当前代码完全相同的结果。I.Value = 10完全是另外一回事。这不会为分配新实例I。但是,I在此处分配一个新实例是重要的一点。
丹尼尔·希尔加斯

2
好的,所以问题是重新分配了“ I”,如果“ I”是可变的,并且我们在不重新分配I的情况下更改了对象,那将是可行的。在此示例中,我们无法做到,因为我是int(不可变的)。
Yshayy 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.