操作符& # 39;? ? & # 39;不能应用于子类类型的操作数

本文关键字:操作数 类型 应用于 操作符 不能 子类 | 更新日期: 2023-09-27 18:11:40

下面的代码给出了Main函数第二行标题中的错误。

public class P {}
public class B : P {}
public class A : P {}
void Main()
{   
    P p = GetA()??GetB();
}
public A GetA() 
{
    return new A();
}
public B GetB()
{
    return new B();
}

对行进行如下简单调整

    p = (P)GetA()??GetB();
    or
    p = GetA()??(P)GetB();

我很好奇为什么编译器不明白这两个都是左侧容器的子类,并允许没有强制转换的操作?

操作符& # 39;? ? & # 39;不能应用于子类类型的操作数

左侧实参的类型必须与右侧的类型兼容,反之亦然。换句话说,必须存在从BA或从AB的隐式转换。

var a = x ?? y;

在上面的例子中,如果存在从yx的隐式转换,则x的类型成为表达式的类型。如果没有从yx的隐式转换,但有从xy的隐式转换,则y的类型成为表达式的类型。如果两个方向都不存在转换,boom;编译错误。来自规范:

表达式的类型a ??B取决于操作数类型之间可用的隐式转换。按偏好排序,a ??b是A0、A或b,其中A是A的类型,b是b的类型(前提是b有类型),如果A是可空类型,A0是A的底层类型,否则为A。具体来说,a ??B的处理如下:

•如果A不是可空类型或引用类型,则发生编译时错误。

•如果A是可空类型,并且存在从b到A0的隐式转换,则结果类型为A0。在运行时,首先求值a。如果a不为空,则将a展开为类型A0,这就是结果。否则,b被求值并转换为类型A0,这就是结果。

•否则,如果存在从b到A的隐式转换,则结果类型为A。在运行时,首先求值A。如果a不为空,则结果为a。否则,b被求值并转换为类型A,这就是结果。

•否则,如果b具有类型b,并且存在从A0到b的隐式转换,则结果类型为b。在运行时,首先计算a。如果a不为空,则a被解封为类型A0(除非a和A0是相同的类型)并转换为类型B,这就是结果。否则,b被求值并成为结果。

•否则,a和b不兼容,并发生编译时错误。

我很好奇为什么编译器不明白这两个都是左侧容器的子类,并允许没有强制转换的操作?

因为这样就可以了:

public class SqlConnection : object {}    
public class Random : object {}
public SqlConnection GetA() { return new SqlConnection(); }
public Random GetB() { return new Random(); }
void Main()
{   
    var p = GetA() ?? GetB();
}

操作符的操作数??应该是相同的类型:

    P a = GetA();
    P b = GetB();
    P p = a ?? b;