为什么C#编译器使用隐式操作符返回的值的父类型来调用重载操作符?

问题描述 投票:3回答:1

我有以下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_AdditionParent 而非 Child.

是否有什么东西是我遗漏的?

我使用的是.NET Framework 4.6.1 C# 7.2。

c# generics operator-overloading implicit-conversion
1个回答
1
投票

当试图确定在特定情况下调用哪个操作符重载时,C#并不考虑每个类中所有可能的用户定义的操作符重载。 它只考虑在操作数之一的(编译时)类型中定义的操作数重载。 它不考虑定义在任何操作数有隐式转换的每个类型中的操作数。


0
投票

我认为 @Servy 的答案是正确的。我只是想通过提供C#规范的链接来扩展它,并添加一个解释。

二进制操作符重载解析 是用来确定一组候选运算符的。

形式为: x op y,其中 op 是一个可过载的二进制运算符。x 是一种类型的表达式 Xy 是一种类型的表达式 Y,处理方式如下。

  • 候选的用户定义运算符集,由... XY 运作 operator op(x,y) 是确定的。该集合由以下所提供的候选运算符的联合组成 X 的候选运算符,并提供 Y的规则来确定。候选的用户定义运营商. 如果 XY 是同一类型,或者如果 XY 是由一个共同的基类型派生出来的,那么共享的候选操作符在组合集中只出现一次。
  • (其他项目不重要)

在这行代码中

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, refout)与相应参数的参数传递方式相同,并且
    • 对于一个值参数或参数数组,隐式转换(隐性转换)存在,从参数到对应参数的类型。
    • (其他项目不重要)

利用这些规则,我们可以得出以下结论。

  • 对于类型 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).
© www.soinside.com 2019 - 2024. All rights reserved.