请考虑以下示例代码:
static void Main(string[] args)
{
bool same = CreateDelegate(1) == CreateDelegate(1);
}
private static Action CreateDelegate(int x)
{
return delegate { int z = x; };
}
您可以想象两个委托实例的比较是相同的,就像使用旧的命名方法(新的Action(MyMethod))时一样。它们并不相同,因为.NET Framework为每个委托实例提供了一个隐藏的闭包实例。由于这两个委托实例都将其Target属性设置为其各自的隐藏实例,因此它们不进行比较。一种可能的解决方案是为生成的IL提供匿名方法,以将当前实例(此指针)存储在委托的目标中。这将允许代表正确比较,并且从调试器的角度来看也有帮助,因为您将看到您的类是目标,而不是隐藏的类。
您可以在我提交给Microsoft的错误中阅读有关此问题的更多信息。错误报告还举例说明了我们使用此功能的原因,以及为什么我们认为应该更改它。如果您认为这也是一个问题,请通过提供评级和验证来帮助支持它。
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=489518
您能看到为什么不应该更改功能的可能原因吗?你认为这是解决问题的最佳方法,还是你建议我采取不同的方式?
答案 0 :(得分:19)
我不是很倾向于认为这是一个“错误”。此外,您还假设CLR中存在一些根本不存在的行为。
这里要理解的重要一点是,每次调用CreateDelegate
方法时,都会返回一个新的匿名方法(并初始化一个新的闭包类)。您似乎熟悉delegate
关键字,以在内部使用某种类型的匿名方法池。 CLR肯定不会这样做。每次调用方法时都会在内存中创建匿名方法的委托(与lambda表达式一样),并且由于在这种情况下,相等运算符当然会比较 references ,这是预期的结果返回false
。
虽然您建议的行为在某些情况下可能会有一些好处,但实施起来可能会非常复杂,并且更有可能导致不可预测的情况。我认为在每次调用时生成新的匿名方法和委托的当前行为是正确的,我怀疑这也是您在Microsoft Connect上获得的反馈。
如果您非常坚持要求您在问题中描述的行为,则始终可以选择memoizing您的CreateDelegate
函数,这样可以确保每次都返回相同的代理相同的参数。实际上,因为这很容易实现,这可能是微软不考虑在CLR中实现它的几个原因之一。
答案 1 :(得分:4)
我不知道这个问题的C#具体细节,但我研究了具有相同行为的VB.Net等效功能。
底线是这种行为是“按设计”,原因如下
首先,在这种情况下,封闭是不可避免的。您在匿名方法中使用了一段本地数据,因此需要关闭来捕获状态。每次调用此方法都必须创建一个新的闭包,原因有很多。因此,每个委托将指向该闭包上的实例方法。
在幕后,匿名方法/表达式由代码中的System.MulticastDelegate
派生实例表示。如果您查看本课程的Equals方法,您会注意到2个重要的细节
这使得连接到不同闭包的2个lambda表达式无法比较为equals。
答案 2 :(得分:3)
编辑:旧答案留在线下的历史价值......
CLR必须解决隐藏类被视为相等的情况,并考虑可以对捕获的变量进行的任何操作。
在这种特殊情况下,捕获的变量(x
)在委托或捕获上下文中都不会更改 - 但我宁愿该语言不需要这种分析的复杂性。语言越复杂,理解起来就越困难。它必须区分这种情况和下面的情况,其中捕获变量的值在每次调用时都会改变 - 在那里,它会产生很大的差异,委托你调用;他们绝不平等。
我认为这种已经很复杂的情况(封闭经常被误解)并不会试图过于“聪明”并找出潜在的平等,这是完全合理的。
IMO,你应该肯定采取不同的路线。这些是Action
的概念独立实例。通过强制代表目标伪造它是一个可怕的黑客IMO。
问题是您在生成的类中捕获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
实例,它们将成为两个代表的目标。他们是不平等的,所以代表们也是不平等的。
答案 3 :(得分:0)
我无法想到我曾经需要这样做的情况。如果我需要比较代理,我总是使用命名委托,否则这样的事情是可能的:
MyObject.MyEvent += delegate { return x + y; };
MyObject.MyEvent -= delegate { return x + y; };
这个例子对于演示这个问题并不是很好,但是我想可能会出现这样的情况:允许这样做可能会破坏设计时预期不允许这样做的现有代码。
我确信有一些内部实现细节也会让这个想法变得糟糕,但我不知道内部是如何实现匿名方法的。
答案 4 :(得分:0)
这种行为是有道理的,因为否则匿名方法会混淆(如果它们具有相同的名称,给定相同的主体)。
您可以将代码更改为:
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&lt; ...&gt; ):
static void Main(){
var action1 = action;
var action2 = action;
bool same = action1 == action2; // TRUE, of course
}
static Action<int> action = (x) => { int z = x; };