null抽象基类/派生类的合并问题

use*_*637 14 c# null coalescing

为什么C#null合并运算符无法解决这个问题?

  Cat c = new Cat();
  Dog d = null;

  Animal a = d ?? c;
Run Code Online (Sandbox Code Playgroud)

这将给出错误

操作员?不能应用于Dog和Cat类型的操作数

鉴于以下编译,这看起来很奇怪.

Animal a = d;
a = c;
Run Code Online (Sandbox Code Playgroud)

下面的上下文代码:

public abstract class Animal
{
  public virtual void MakeNoise()
  {        
    Console.WriteLine("noise");
  }    
}

public class Dog : Animal
{
  public override void MakeNoise()
  {
     Console.WriteLine("wuff");
  }
}

public class Cat : Animal
{
  public override void MakeNoise()
  {
    Console.WriteLine("miaow");
  }
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 19

C#的一个微妙的设计规则是C#永远不会推断出一个不在表达式中的类型.由于Animal不在表达式中d ?? c,因此类型Animal不是选择.

这个原则适用于C#推断类型的所有地方.例如:

var x = new[] { dog1, dog2, dog3, dog4, cat }; // Error
Run Code Online (Sandbox Code Playgroud)

编译器没有说"这必须是一群动物",它说"我认为你犯了一个错误".

这是更一般的设计规则的特定版本,"当程序看起来不明确而不是做出可能错误的猜测时给出错误".

这里发挥作用的另一个设计规则是:从内到外的类型的原因,而不是从外到内的类型.也就是说,您应该能够通过查看其中的部分来计算表达式中所有内容的类型,而无需查看其上下文.在你的例子中,Animal来自??表达之外; 我们应该能够弄清楚??表达式的类型,然后问问题"这种类型是否与上下文兼容?" 而不是走另一条路说"这里是上下文 - 现在找出??表达式的类型."

这条规则是合理的,因为上下文不清楚.在你的情况下,背景非常清楚; 事情被分配给Animal.但是关于:

var x = a ?? b;
Run Code Online (Sandbox Code Playgroud)

现在x推断出类型.我们不知道上下文的类型,因为这就是我们正在研究的内容.要么

M(a ?? b)
Run Code Online (Sandbox Code Playgroud)

可能有二十多个重载,M我们需要根据参数的类型知道要选择哪一个.很难以另一种方式推理并说"上下文可能是这些事情a??b中的一个; 在每个上下文中进行评估并计算出它的类型".

这条规则被违反了lambda表达式,这基于他们的背景进行分析.使代码正确和有效是非常困难的; 它花了我一年的工作中最好的一部分.编译器团队可以更快,更好地完成更多功能,而不需要在不需要的情况下承担这些费用.


C.E*_*uis 9

之前分配Animal a,c和d仍CatDog分别.以下工作方式符合您的预期:

Animal a = (Animal)c ?? (Animal)d;
Run Code Online (Sandbox Code Playgroud)

  • 可选地,可以省略其中之一。我不确定这如何影响可读性。 (2认同)

dca*_*tro 5

出于同样的原因,它Animal a = (true)? d : c;不起作用(使用三元运算符)。

根据C#规范,表达式的类型推断如下(引用Eric Lippert):

?:运算符的第二和第三操作数控制条件表达式的类型。令X和Y为第二和第三操作数的类型。然后,

  • 如果X和Y是同一类型,则这是条件表达式的类型。
  • 否则,如果存在从X到Y的隐式转换,但不存在从Y到X的隐式转换,则Y是条件表达式的类型。
  • 否则,如果存在从Y到X的隐式转换,但不存在从X到Y的隐式转换,则X是条件表达式的类型。
  • 否则,将无法确定表达式类型,并且会发生编译时错误。

由于不存在从Dog到Cat或从Cat到Dog的隐式转换,因此无法推断类型。相同的原则适用于空合并运算符。

编辑

为什么无效合并会关心猫和狗之间的关系,而不仅关心猫和动物以及狗和动物之间的关系?

关于为什么编译器不仅仅意识到两个运算符都是Animals:

一罐蠕虫太大了。我们喜欢这样的原理,即表达式的类型必须是表达式中某种事物的类型。