隐式方法组转换问题(第2部分)

时间:2012-01-20 09:40:05

标签: c# .net operator-overloading

this question简化并消除了LinqPad的可能影响(没有密集),这样一个简单的控制台应用程序:

public class Program
{
    static void M() { }    
    static void Main(string[] args)
    {
        Action a = new Action(M);
        Delegate b = new Action(M);
        Console.WriteLine(a == b);      //got False here
        Console.Read();
    }        
}

“false”来自上面代码的CIL中的运算符ceq(有关详细信息,请访问原始问题)。所以我的问题是:

(1)为什么==正在转换为ceq而不是call Delegate Equals

这里我不关心Delegate和Action之间的(un)包装。最后,在评估a == b时,a的类型为Action,而b为Delegate。来自规范:

  

7.3.4二进制运算符重载决策

     

x op y形式的一个操作,其中op是一个可重载的二元运算符,x是一个表达式   类型为X,y为Y类型的表达式,按如下方式处理:

     

•由X和Y提供的候选用户定义运算符集   确定操作运算符op(x,y)。该集包括   X和候选人提供的候选运营商联盟   Y提供的运营商,每个运营商都使用§7.3.5的规则确定。如果   X和Y是相同的类型,或者如果X和Y来自共同的   基类型,然后共享候选运算符只出现在组合中   设置一次。

     

•如果不是候选用户定义的运算符集   空,然后这成为了候选运算符的集合   操作。否则,预定义的二元运算符op   实现,包括他们提升的形式,成为一组   操作的候选操作员。预定义的实现   给定运算符的值在运算符的描述中指定   (§7.8至§7.12)。

     

•§7.5.3的重载决策规则是   应用于候选运算符集以选择最佳运算符   关于参数列表(x,y),该运算符变为   重载解析过程的结果。如果超载分辨率   如果无法选择单个最佳运算符,则会发生绑定时错误。

     

7.3.5候选用户定义的运算符

     

给定类型T和操作运算符op(A),其中op是可重载运算符,A是参数列表,由候选用户定义的运算符集提供者   运算符op(A)的T确定如下:

     

•确定类型   T0。如果T是可空类型,则T0是其基础类型,否则为T0   等于T.

     

•对于T0中的所有操作员操作声明并且全部解除   如果至少有一个运营商适用,则为此类运营商的形式   (§7.5.3.1)关于参数列表A,然后是集合   候选运营商由T0中的所有适用运营商组成。

     

•否则,如果T0是对象,则候选运算符集为空。

     

•否则,T0提供的候选运算符集就是集合   候选运营商提供的直接基类T0,或   如果T0是类型参数,则为有效基类T0。

根据规范,a和b具有相同的基类Delegate,显然应在此处应用==中定义的运算符规则Delegate(运算符==调用Delegate.Equals实质上)。但现在看起来用户定义的运算符的候选列表是空的,并且最后应用了Object ==

(2)FCL代码是否应遵守C#语言规范?如果不是,我的第一个问题毫无意义,因为有些东西是特别对待的。然后我们可以回答所有这些问题,“哦,这是FCL的特殊处理,他们可以做我们做不到的事情。规范适用于外部程序员,不要太傻了。”

4 个答案:

答案 0 :(得分:5)

编译器与代表的工作方式非常不同且不同寻常。有很多隐式处理。 请注意,本指南中的“常用基本类型”规则适用于“用户定义的运算符”。代表是内部和系统。例如,您可以编写Action a = M;而不是Action a = new Action(M);。之后您可以添加a += M;。检查CIL中发生了什么,这是第一次有趣。

更多:比较代表是危险的,也是非常重要的。每个代表实际上都是多播委托。您可以向同一个委托添加多个函数指针。代表[L(); M(); N();]是否等于委派[M();]?函数指针包含类实例(例如方法)。 [a.M();]是否等于[b.M();]?所有这些都取决于案例,并且比较实现需要逐步调用调用列表。

从公共基类型代表继承委托是隐式的,您可以在其他方案中遇到此问题,例如泛型约束:您不能将Delegate指定为泛型参数T的约束。这里编译器明确拒绝这一点。关于创建自己的类,从Delegate继承。

这是两个问题的答案 - '委托'不是纯粹的FCL,而是与编译器紧密结合。如果您真的想要Microsoft的委托比较器行为 - 只需显式调用Equals(a, b)

答案 1 :(得分:4)

有两种类型的运算符:用户定义的运算符和预定义的运算符。第7.3.5节“候选用户定义的运算符”不适用于预定义的运算符。 例如,decimal上的运算符看起来像反编译器中的用户定义运算符,但C#将它们视为预定义运算符并对其应用数字提升(数字提升不应用于用户定义的运算符)。

第7.10.8节“委托相等运算符”将operator ==(Delegate, Delegate)定义为预定义运算符,因此我认为有关用户定义运算符的所有规则都不适用于此运算符(尽管这不是在这种情况下,规范中100%清除,只要用户定义的运算符,预定义的运算符就不会应用。

Every delegate type implicitly provides the following predefined comparison operators: 
bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y); 

System.Delegate本身不被视为委托类型,因此重载解析的唯一候选者是operator ==(object, object)

答案 2 :(得分:3)

  

警告CS0253:可能的非预期参考比较;要获得值比较,请在右侧输入“System.Action”

这是对C#代码的警告。不要忽视该警告,C#团队非常清楚他们为此比较生成的代码是意外的。他们没有拥有来生成该代码,他们可以很容易地完成您的预期。像这样的代码:

Module Module1
    Sub M()
    End Sub

    Sub Main()
        Dim a = New Action(AddressOf M)
        Dim b = DirectCast(New Action(AddressOf M), [Delegate])
        Console.WriteLine(a = b)      ''got True here
        Console.Read()
    End Sub
End Module

哪个生成几乎相同的MSIL,除了你得到ceq而不是:

 IL_001d:  call bool [mscorlib]System.Delegate::op_Equality(class [mscorlib]System.Delegate,
                                                            class [mscorlib]System.Delegate)

您希望C#代码能做什么。这是VB.NET代码,万一你不认识它。否则,微软保留两种主要托管语言的原因,即使它们具有非常相似的功能。但具有非常不同的可用性选择。每当生成代码的方式不止一种时,C#团队就一直选择性能,VB.NET团队始终为方便

性能当然是关键所在,比较委托对象昂贵。这些规则在Ecma-335第II.14.6.1节中有详细说明。但是你可以自己推理,有很多检查要做。它需要检查委托目标对象是否兼容。并且对于每个参数,它必须检查该值是否可转换。 C#团队不想隐藏的费用。

并且没有,你得到警告提醒你他们做出了不直观的选择。

答案 3 :(得分:-1)

这里的关键是==运算符和Equals类型的Delegate方法是两个不同的东西。对于引用类型,==查看两个引用是否指向同一个对象,除非重写==运算符(请参阅:== Operator (C#))。

由于您正在创建两个不同的Action对象,即使它们在内部调用相同的方法,它们也是内存中不同位置的不同对象,并且不是值或string类型,因此==在这种情况下是ReferenceEquals,并且不会调用Delegate.Equals方法,该方法已被覆盖以查看这两个对象是否执行相同的操作。对于string以外的参考类型,这是==Equals的默认行为。