我有以下2个类。
public class Parent
{
public static Parent operator +(Parent l, Parent r)
{
return new Parent(); //do something meaningful
}
}
public class Child: Parent
{
public static Child operator +(Child l, Parent r)
{
return new Child(); //do something meaningful, child related
}
}
然后我有一个包装类,使用 implicit
转换来返回封装的值。
public class Wrapper<T>
{
private T value;
public T Value => value;
public static implicit operator T(Wrapper<T> wrapper)
{
return wrapper.value;
}
}
然后我把这两者结合起来,如下所示。
public class Usage
{
private Parent someField;
private Wrapper<Child> wrappedValue;
public void UseOperatorWithImplicitConversion()
{
//Child sum1 = wrappedValue + someField; //<-- compilation error
Parent sum2 = wrappedValue + someField;
Child temp = wrappedValue; //works but defeats the purpose of reduced verbosity
Child sum3 = temp + someField;
}
}
我期待的是... sum1
行工作。我看了一下生成的IL,似乎类型都在那里。
IL_0001: ldarg.0 // this
IL_0002: ldfld class Example.Wrapper`1<class Example.Child> Example.Usage::wrappedValue
IL_0007: call !0/*class Example.Child*/ class Example.Wrapper`1<class Example.Child>::op_Implicit(class Example.Wrapper`1<!0/*class Example.Child*/>)
IL_000c: ldarg.0 // this
IL_000d: ldfld class Example.Parent Example.Usage::someField
IL_0012: call class Example.Parent Example.Parent::op_Addition(class Example.Parent, class Example.Parent)
IL_0017: stloc.0 // sum2
虽然... IL_0012
是呼吁 op_Addition
的 Parent
而非 Child
.
是否有什么东西是我遗漏的?
我使用的是.NET Framework 4.6.1 C# 7.2。
当试图确定在特定情况下调用哪个操作符重载时,C#并不考虑每个类中所有可能的用户定义的操作符重载。 它只考虑在操作数之一的(编译时)类型中定义的操作数重载。 它不考虑定义在任何操作数有隐式转换的每个类型中的操作数。
我认为 @Servy 的答案是正确的。我只是想通过提供C#规范的链接来扩展它,并添加一个解释。
二进制操作符重载解析 是用来确定一组候选运算符的。
形式为:
x op y
,其中op
是一个可过载的二进制运算符。x
是一种类型的表达式X
和y
是一种类型的表达式Y
,处理方式如下。
- 候选的用户定义运算符集,由...
X
和Y
运作operator op(x,y)
是确定的。该集合由以下所提供的候选运算符的联合组成X
的候选运算符,并提供Y
的规则来确定。候选的用户定义运营商. 如果X
和Y
是同一类型,或者如果X
和Y
是由一个共同的基类型派生出来的,那么共享的候选操作符在组合集中只出现一次。- (其他项目不重要)
在这行代码中
Parent sum2 = wrappedValue + someField;
x
是一种类型的表达式 Wrapper<Child>
和 y
是一种类型的表达式 Parent
.
根据二元运算符过载解析规则,候选运算符集是这两种类型所提供的运算符的联合。对于每一种类型的候选运算符集都是通过以下规则来确定的。候选的用户定义运营商:
给定一个类型
T
和手术operator op(A)
,其中op
是一个可过载操作符,并且A
是一个参数列表,是由用户提供的候选用户自定义运算符集。T
对于operator op(A)
确定如下:
- 对于所有
operator op
中的声明T
以及这些运算符的所有提升形式,如果至少有一个运算符适用的话(适用功能成员)与参数列表A
那么,候选算子集由所有这些适用的算子组成。T
.- (其他项目不重要)
和 适用功能成员:
一个函数成员据说是一个适用于参数列表的函数成员。
A
当以下所有条件为真时。
- 对于每个参数
A
,参数的参数传递方式(即。value
,ref
或out
)与相应参数的参数传递方式相同,并且
- 对于一个值参数或参数数组,隐式转换(隐性转换)存在,从参数到对应参数的类型。
- (其他项目不重要)
利用这些规则,我们可以得出以下结论。
Wrapper<Child>
一组候选运算符 operator +(Wrapper<Child>, Parent)
是空的。Parent
一组候选运算符 operator +(Wrapper<Child>, Parent)
中定义的一个运算符组成。Parent
: operator +(Parent, Parent)
. 该运算符适用于(根据 适用功能成员)作为候选运算符,因为有一个隐式转换,从 Wrapper<Child>
到 Parent
. 所以我们有一个候选操作者 operator +(Parent, Parent)
因此,它适用于我们的情况。
同时我们可以得出以下结论。
operator +(Child, Parent)
从类 Child
不被认为是候选运算符。operator +(Parent, Parent)
被定义为适用于该案 operator +(Wrapper<Child>, Parent)
.