Che*_*hen 14 .net c# operator-overloading
从这个问题简化并摆脱了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();
    }        
}
操作员ceq在上述代码的CIL中产生"错误" (有关详细信息,请访问原始问题).所以我的问题是:
(1)为什么==要翻译ceq而不是call Delegate Equals?
在这里,我不关心Delegate和Action之间的(un)包装.最后,在评估时a == b,a是类型,Action而b是a Delegate.从规格:
7.3.4二元运算符重载决策
形式为x op y的操作,其中op是可重载的二元运算符,x是类型X的表达式,y是类型Y的表达式,按如下方式处理:
•确定由操作运算符op(x,y)的X和Y提供的候选用户定义运算符集.该集合由X提供的候选运算符和Y提供的候选运算符组合而成,每个运算符使用§7.3.5的规则确定.如果X和Y是相同类型,或者如果X和Y是从公共基类型派生的,则共享候选运算符仅出现在组合集中一次.
•如果候选用户定义的运算符集不为空,则此变为该操作的候选运算符集.否则,预定义的二元运算符op实现(包括它们的提升形式)将成为该操作的候选运算符集.给定运算符的预定义实现在运算符的描述中指定(第7.7节到第7.12节).
•§7.5.3的重载决策规则应用于候选运算符集合,以选择与参数列表(x,y)相关的最佳运算符,并且此运算符成为重载解析过程的结果.如果重载决策未能选择单个最佳运算符,则会发生绑定时错误.
7.3.5候选用户定义的运算符
给定类型T和操作运算符op(A),其中op是可重载运算符,A是参数列表,由T为运算符op(A)提供的候选用户定义运算符集合如下确定:
•确定类型T0.如果T是可空类型,则T0是其基础类型,否则T0等于T.
•对于T0中的所有运算符op声明和此类运算符的所有提升形式,如果关于参数列表A至少有一个运算符适用(第7.5.3.1节),则候选运算符集包含所有此类适用运算符. T0.
•否则,如果T0是对象,则候选运算符集为空.
•否则,T0提供的候选运算符集合是由T0的直接基类提供的候选运算符集合,或者如果T0是类型参数则是T0的有效基类.
从规范中,a和b具有相同的基类Delegate,显然应该在这里应用==定义的运算符规则Delegate(operator ==从本质上调用Delegate.Equals).但现在看起来用户定义的运算符的候选列表是空的,最后Object ==应用.
(2)FCL代码是否应遵守C#语言规范?如果不是,我的第一个问题毫无意义,因为有些东西是特别对待的.然后我们可以回答所有这些问题,"哦,这是FCL的特殊处理,他们可以做我们做不到的事情.规范适用于外部程序员,不要傻".
编译器与代表的工作方式非常不同和不同寻常.有很多隐式处理.请注意,本指南中的"公共基本类型"规则适用于"用户定义的运算符".代表是内部和系统.例如,您可以编写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)
警告 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 代码,以防您不认识它。否则,即使它们具有非常相似的功能,Microsoft 仍保留两种主要的托管语言的原因。但是有非常不同的可用性选择。每当有不止一种生成代码的方法时,C# 团队始终选择性能,而 VB.NET 团队始终选择方便。
性能当然是这里的关键,比较委托对象是昂贵的。这些规则在 Ecma-335 第 II.14.6.1 节中有详细说明。但是你可以自己推理出来,有很多检查要做。它需要检查委托目标对象是否兼容。对于每个参数,它必须检查该值是否可转换。C# 团队不想隐藏的费用。
而不是,您会收到警告,提醒您他们做出了不直观的选择。.
有两种类型的运算符:用户定义的运算符和预定义的运算符。第 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).