隐式方法组转换问题(第2部分)
从这个问题简化并摆脱了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二元运算符重载决策
forms为x op y的操作,其中op是可重载的二元运算符,x是类型X的表达式,y是类型Y的表达式,按如下方式处理:
•确定由操作运算符op(x,y)的X和Y提供的候选用户定义运算符集。 该集合由X提供的候选运算符与Y提供的候选运算符的并集组成,每个运算符使用§7.3.5的规则确定。 如果X和Y是相同的类型,或者如果X和Y是从公共基类型派生的,则共享候选运算符仅出现在组合集中一次。
•如果候选用户定义的运算符集不为空,则此变为该操作的候选运算符集。 否则,预定义的二元运算符op实现(包括它们的提升forms)成为该操作的候选运算符集。 给定运算符的预定义实现在运算符的描述中指定(第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声明和此类运算符的所有提升forms,如果对于参数列表A至少有一个运算符适用(第7.5.3.1节),则候选运算符集由所有此类适用运算符组成。 T0。
•否则,如果T0是对象,则候选运算符集为空。
•否则,T0提供的候选运算符集合是由T0的直接基类提供的候选运算符集合,或者如果T0是类型参数则是T0的有效基类。
从规范中,a和b具有相同的基类Delegate
,显然在Delegate
定义的运算符规则==
应该在这里应用(operator ==从本质上调用Delegate.Equals)。 但现在看起来用户定义的运算符的候选列表是空的,最后应用了Object ==
。
(2)FCL代码是否应遵守C#语言规范? 如果不是,我的第一个问题毫无意义,因为有些东西是特别对待的。 然后我们可以回答所有这些问题,“哦,这是FCL的特殊处理,他们可以做我们做不到的事情。规范适用于外部程序员,不要傻”。
有两种类型的运算符:用户定义的运算符和预定义的运算符。 第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)
是operator ==(object, object)
。
编译器与代表的工作方式非常不同和不同寻常。 有很多隐式处理。 请注意,本指南中的“公共基本类型”规则适用于“用户定义的运算符”。 代表是内部和系统。 例如,您可以编写Action a = M;
代替Action a = new Action(M);
。 你可以加a += M;
之后。 检查CIL中发生了什么,这是第一次有趣。
更多:比较代表是危险的,不重要的。 每个代表实际上都是多播委托。 您可以向同一个委托添加多个函数指针。 代表是否[L(); M(); N();]
[L(); M(); N();]
[L(); M(); N();]
等于委托[M();]
? 函数指针包含类实例(例如方法)。 [aM();]
是否等于[bM();]
? 所有这些都取决于案例,并且比较实现需要逐步调用调用列表。
委托从公共基类型委托inheritance是隐式的,你可以在另一个场景中遇到这个问题,例如generics约束:你不能将Delegate指定为generics参数T的约束。这里编译器明确拒绝这一点。 关于创建自己的类,从Delegateinheritance的相同。
这是两个问题的答案 – ‘委托’不是纯粹的FCL,而是与编译器紧密结合。 如果你真的想要微软的委托比较器行为 – 只需要明确调用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代码,万一你不认识它。 否则,微软保留两种主要托管语言的原因,即使它们具有非常相似的function。 但是具有非常不同的可用性选择。 每当生成代码的方式不止一种时,C#团队就会一直选择性能 ,VB.NET团队始终如一。
并且性能当然是关键,比较委托对象是昂贵的 。 这些规则在Ecma-335第II.14.6.1节中有详细说明。 但是你可以自己推理,有很多检查要做。 它需要检查委托目标对象是否兼容。 并且对于每个参数,它必须检查该值是否可转换。 C#团队不想隐藏的费用。
并且没有,你得到警告提醒你他们做出了不直观的选择。 。
这里的关键是==
运算符和Delegate
类型的Equals
方法是两个不同的东西。 对于引用类型, ==
查看两个引用是否指向同一个对象,除非重写==
运算符(请参阅: ==运算符(C#) )。
由于您正在创建两个不同的Action
对象,即使它们在内部调用相同的方法,它们也是内存中不同位置的不同对象,并且不是值或string
类型,所以==
在这种情况下是ReferenceEquals
,并且不调用Delegate.Equals
方法,该方法已被重写以查看两个对象是否执行相同的操作。 对于除string
以外的引用类型,这是==
或Equals
的默认行为。