为同一个匿名方法创建两个委托实例不相等

发布于 2024-08-04 21:54:50 字数 864 浏览 6 评论 0原文

考虑以下示例代码:

static void Main(string[] args)
{
   bool same = CreateDelegate(1) == CreateDelegate(1);
}

private static Action CreateDelegate(int x)
{
   return delegate { int z = x; };
}

您可以想象两个委托实例比较起来是相等的,就像使用良好的旧命名方法方法(new Action(MyMethod))时一样。它们比较起来并不相等,因为 .NET Framework 为每个委托实例提供了一个隐藏的闭包实例。由于这两个委托实例各自将其 Target 属性设置为各自的隐藏实例,因此它们不会进行比较。一种可能的解决方案是为匿名方法生成的 IL 将当前实例(this 指针)存储在委托的目标中。这将允许委托进行正确的比较,并且从调试器的角度来看也有帮助,因为您将看到您的类是目标,而不是隐藏的类。

您可以在我提交给 Microsoft 的错误中阅读有关此问题的更多信息。错误报告还举例说明了我们为什么使用此功能,以及为什么我们认为应该更改它。如果您也认为这也是一个问题,请通过提供评级和验证来帮助支持它。

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx ?FeedbackID=489518

您能看出不应更改功能的任何可能原因吗?您认为这是解决问题的最佳行动方案,还是建议我采取不同的路线?

Consider the following example code:

static void Main(string[] args)
{
   bool same = CreateDelegate(1) == CreateDelegate(1);
}

private static Action CreateDelegate(int x)
{
   return delegate { int z = x; };
}

You would imagine that the two delegate instances would compare to be equal, just as they would when using the good old named method approach (new Action(MyMethod)). They do not compare to be equal because the .NET Framework provides a hidden closure instance per delegate instance. Since those two delegate instances each have their Target properties set to their individual hidden instance, they do not compare. One possible solution is for the generated IL for an anonymous method to store the current instance (this pointer) in the target of the delegate. This will allow the delegates to compare correctly, and also helps from a debugger standpoint since you will see your class being the target, instead of a hidden class.

You can read more about this issue in the bug I submitted to Microsoft. The bug report also gives an example of why we are using this functionality, and why we feel it should be changed. If you feel this to be an issue as well, please help support it by providing rating and validation.

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=489518

Can you see any possible reason why the functionality should not be changed? Do you feel this was the best course of action to get the issue resolved, or do you recommend that I should take a different route?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

倾城°AllureLove 2024-08-11 21:54:50

我不太倾向于认为这是一个“错误”。此外,您似乎假设 CLR 中的某些行为根本不存在。

这里需要理解的重要一点是,每次调用 CreateDelegate 方法时,您都会返回一个新的匿名方法(并初始化一个新的闭包类)。看来您正在熟练使用delegate关键字来在内部使用某种类型的匿名方法池。 CLR 当然不会这样做。每次调用该方法时,都会在内存中创建匿名方法的委托(与 lambda 表达式一样),并且由于相等运算符在这种情况下当然会比较引用,因此这是预期的结果返回false

尽管您建议的行为在某些情况下可能有一些好处,但实施起来可能会相当复杂,并且更有可能导致不可预测的情况。我认为当前在每次调用时生成新的匿名方法和委托的行为是正确的,并且我怀疑这也是您在 Microsoft Connect 上得到的反馈。

如果您非常坚持要执行问题中描述的行为,则始终可以选择记忆 您的 CreateDelegate 函数,这将确保每次为相同的参数返回相同的委托。事实上,由于这非常容易实现,这可能是 Microsoft 不考虑在 CLR 中实现它的几个原因之一。

I'm not so inclined to think this is a "bug". It appears moreover that you're assuming some behaviour in the CLR that simply does not exist.

The important thing to understand here is that you are returning a new anonymous method (and initialising a new closure class) each time you call the CreateDelegate method. It seems that you are experting the delegate keyword to use some sort of pool for anonymous methods internally. The CLR certainly does not do this. A delegate to the anonymous method (as with a lambda expression) is created in memory each time you call the method, and since the equality operator does of course compare references in this situation, it is the expected result to return false.

Although your suggested behaviour may have some benefits in certain contexts, it would probably be quite complicated to implement, and would more likely lead to unpredictable scenarios. I think the current behaviour of generating a new anonymous method and delegate on each call is the right one, and I suspect this is the feedback you will get on Microsoft Connect as well.

If you are quite insistent on having the behaviour you described in your question, there is always the option of memoizing your CreateDelegate function, which would insure that the same delegate is returned each time for the same parameters. Indeed, because this is so easy to implement, it is probably one of the several reasons why Microsoft did not consider implementing it in the CLR.

浅唱々樱花落 2024-08-11 21:54:50

编辑:旧答案留给线下的历史价值...

CLR 必须计算出隐藏类可以被认为是相等的情况,同时考虑到可以对捕获的变量执行的任何操作。

在这种特殊情况下,捕获的变量 (x) 在委托内或捕获上下文中都不会更改 - 但我希望该语言不需要这种复杂的分析。语言越复杂,就越难理解。必须区分这种情况和下面的情况,其中捕获的变量的值在每次调用时都会更改 - 在那里,您调用哪个委托会产生很大的差异;他们绝不是平等的。

我认为这种已经很复杂的情况(闭包经常被误解)不试图太“聪明”并找出潜在的平等是完全明智的。

IMO,您绝对应该采取不同的路线。这些在概念上是Action独立实例。在我看来,通过强制委托目标来伪造它是一种可怕的黑客行为。


问题是您正在生成的类中捕获 x 的值。这两个 x 变量是独立的,因此它们是不平等的委托。下面是一个演示独立性的示例:

using System;

class Test
{
    static void Main(string[] args)
    {
        Action first = CreateDelegate(1);
        Action second = CreateDelegate(1);
        first();
        first();
        first();
        first();
        second();
        second();
    }

    private static Action CreateDelegate(int x)
    {
        return delegate 
        { 
            Console.WriteLine(x);
            x++;
        };
    }
}

输出:

1
2
3
4
1
2

编辑: 从另一个角度来看,您的原始程序相当于:

using System;

class Test
{
    static void Main(string[] args)
    {
        bool same = CreateDelegate(1) == CreateDelegate(1);
    }

    private static Action CreateDelegate(int x)
    {
        return new NestedClass(x).ActionMethod;
    }

    private class Nested
    {
        private int x;

        internal Nested(int x)
        {
            this.x = x;
        }

        internal ActionMethod()
        {
            int z = x;
        }
    }
}

如您所知,将创建两个单独的 Nested 实例,并且它们将成为两位代表的目标。他们是不平等的,因此代表也是不平等的。

EDIT: Old answer left for historical value below the line...

The CLR would have to work out the cases in which the hidden classes could be considered equal, taking into account anything that could be done with the captured variables.

In this particular case, the captured variable (x) isn't changed either within the delegate or in the capturing context - but I'd rather the language didn't require this sort of complexity of analysis. The more complicated the language is, the harder it is to understand. It would have to distinguish between this case and the one below, where the captured variable's value is changed on each invocation - there, it makes a great deal of difference which delegate you call; they are in no way equal.

I think it's entirely sensible that this already-complex situation (closures are frequently misunderstood) doesn't try to be too "clever" and work out potential equality.

IMO, you should definitely take a different route. These are conceptually independent instances of Action. Faking it by coercing the delegate targets is a horrible hack IMO.


The problem is that you're capturing the value of x in a generated class. The two x variables are independent, so they're unequal delegates. Here's an example demonstrating the independence:

using System;

class Test
{
    static void Main(string[] args)
    {
        Action first = CreateDelegate(1);
        Action second = CreateDelegate(1);
        first();
        first();
        first();
        first();
        second();
        second();
    }

    private static Action CreateDelegate(int x)
    {
        return delegate 
        { 
            Console.WriteLine(x);
            x++;
        };
    }
}

Output:

1
2
3
4
1
2

EDIT: To look at it another way, your original program was the equivalent of:

using System;

class Test
{
    static void Main(string[] args)
    {
        bool same = CreateDelegate(1) == CreateDelegate(1);
    }

    private static Action CreateDelegate(int x)
    {
        return new NestedClass(x).ActionMethod;
    }

    private class Nested
    {
        private int x;

        internal Nested(int x)
        {
            this.x = x;
        }

        internal ActionMethod()
        {
            int z = x;
        }
    }
}

As you can tell, two separate instances of Nested will be created, and they will be the targets for the two delegates. They are unequal, so the delegates are unequal too.

不寐倦长更 2024-08-11 21:54:50

我不知道这个问题的 C# 具体细节,但我研究了具有相同行为的 VB.Net 等效功能。

最重要的是,这种行为是“设计使然”的,原因如下

:首先,在这种情况下,关闭是不可避免的。您在匿名方法中使用了一段本地数据,因此需要闭包来捕获状态。由于多种原因,每次调用此方法都必须创建一个新的闭包。因此,每个委托将指向该闭包上的一个实例方法。

在底层,匿名方法/表达式由代码中的 System.MulticastDelegate 派生实例表示。如果您查看此类的 Equals 方法,您会注意到 2 个重要细节

  • 它是密封的,因此派生委托无法更改 equals 行为
  • Equals 方法的一部分对对象进行引用比较

这使得不可能2 个 lambda 表达式附加到不同的闭包以进行相等比较。

I don't know about the C# specific details of this problem but I worked on the VB.Net equivalent feature which has the same behavior.

The bottom line is this behavior is "By Design" for the following reasons

The first is that in this scenario a closure is unavoidable. You have used a piece of local data within an anonymous method and hence a closure is necessary to capture the state. Every call to this method must create a new closure for a number of reasons. Therefore each delegate will point to an instance method on that closure.

Under the hood a anonymous method / expression is represented by a System.MulticastDelegate derived instance in code. If you look at the Equals method of this class you will notice 2 important details

  • It is sealed so there is no way for a derived delegate to change the equals behavior
  • Part of the Equals method does a reference comparison on the objects

This makes it impossible for 2 lambda expressions which are attached to different closures to compare as equals.

咋地 2024-08-11 21:54:50

我想不出在什么情况下我需要这样做。如果我需要比较委托,我总是使用命名委托,否则可能会出现这样的情况:

MyObject.MyEvent += delegate { return x + y; };

MyObject.MyEvent -= delegate { return x + y; };

这个示例不太适合演示问题,但我想象可能存在一种情况,允许这样做可能会破坏现有的代码设计时就预期这是不允许的。

我确信内部实现细节也使这成为一个坏主意,但我不确切知道匿名方法在内部是如何实现的。

I can't think of a situation where I've ever needed to do that. If I need to compare delegates I always use named delegates, otherwise something like this would be possible:

MyObject.MyEvent += delegate { return x + y; };

MyObject.MyEvent -= delegate { return x + y; };

This example isn't great for demonstrating the issue, but I would imagine that there could be a situation where allowing this could break existing code that was designed with the expectation that this is not allowed.

I'm sure there are internal implementation details that also make this a bad idea, but I don't know exactly how anonymous methods are implemented internally.

尸血腥色 2024-08-11 21:54:50

这种行为是有道理的,因为否则匿名方法会混淆(如果它们具有相同的名称,给定相同的主体)。

您可以将代码更改为:

static void Main(){   
    bool same = CreateDelegate(1) == CreateDelegate(1);
}

static Action<int> action = (x) => { int z = x; };

private static Action<int> CreateDelegate(int x){
    return action;
}

或者,最好是,因为这是一种不好的使用方式(加上您正在比较结果,并且 Action 没有返回值...使用 Func<...> 如果您想要返回一个值):

static void Main(){
    var action1 = action;
    var action2 = action;
    bool same = action1 == action2;  // TRUE, of course
}

static Action<int> action = (x) => { int z = x; };

This behaviour makes sense because otherwise anonymous methods would get mixed up (if they had the same name, given the same body).

You could change your code to this:

static void Main(){   
    bool same = CreateDelegate(1) == CreateDelegate(1);
}

static Action<int> action = (x) => { int z = x; };

private static Action<int> CreateDelegate(int x){
    return action;
}

Or, preferably, since that's a bad way to use it (plus you were comparing the result, and Action doesn't have a return value ... use Func<...> if you want to return a value):

static void Main(){
    var action1 = action;
    var action2 = action;
    bool same = action1 == action2;  // TRUE, of course
}

static Action<int> action = (x) => { int z = x; };
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文