14

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

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

  Animal a = d ?? c;

这将给出错误

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

考虑到以下编译,这似乎很奇怪。

Animal a = d;
a = c;

下面的上下文代码:

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");
  }
}
4

4 回答 4

20

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

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

var x = new[] { dog1, dog2, dog3, dog4, cat }; // Error

编译器没有说“这一定是一个动物数组”,而是说“我认为你犯了一个错误”。

这是更通用设计规则的特定版本,即“在程序看起来模棱两可时给出错误,而不是做出可能错误的猜测”。

另一个在这里发挥作用的设计规则是:类型的推理是从内到外,而不是从外到内。也就是说,您应该能够通过查看表达式的各个部分来确定表达式中所有内容的类型,而无需查看其上下文。在您的示例中,Animal来自??表达式之外;我们应该能够弄清楚??表达式的类型是什么,然后提出问题“这种类型与上下文兼容吗?” 而不是反过来说“这是上下文——现在算出??表达式的类型”。

这个规则是合理的,因为很多时候上下文是不清楚的。在您的情况下,上下文非常清楚;东西被分配给Animal. 但是关于:

var x = a ?? b;

现在x正在推断类型。我们不知道上下文的类型,因为这是我们正在解决的问题。或者

M(a ?? b)

可能有两打重载,M我们需要根据参数的类型知道要选择哪一个。很难以另一种方式推理并说“上下文可能是这十几件事之一;a??b在每个上下文中评估并计算出它的类型”。

lambdas 违反了该规则,lambdas是根据其上下文进行分析的让代码既正确又高效是非常困难的。我花了一年工作的大部分时间。编译器团队可以通过在不需要的地方不承担费用来更快更好地完成更多功能。

于 2013-11-14T15:49:03.407 回答
9

在将其分配给Animal a之前,c 和 d 分别是CatDog。以下确实按您期望的方式工作:

Animal a = (Animal)c ?? (Animal)d;
于 2013-11-14T10:05:48.697 回答
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 的隐式转换,因此无法推断类型。相同的原则适用于空合并运算符。

编辑

为什么 null coalesce 关心 Cat 和 Dog 之间的关系,而不仅仅是 Cat 和 Animal 以及 Dog 和 Animal 之间的关系?

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

一罐蠕虫太大了。我们喜欢表达式的类型必须是表达式中某物的类型这一原则。

于 2013-11-14T10:18:19.777 回答
3

它失败了,因为c不能d隐式转换,反之亦然。显然Cat不是一个DogDog也不是Cat一个。

尝试这个

Animal a = (Animal)d ?? c;

??现在我们说is的左侧操作数的编译器,是的,它可以将“狗”转换为“动物”,并且isAnimal的右侧和侧操作数也可以转换为“动物”。编译器现在很高兴:)??Cat

于 2013-11-14T10:08:52.103 回答