为什么C#null合并运算符无法解决这个问题?
Cat c = new Cat();
Dog d = null;
Animal a = d ?? c;
这将给出错误
操作员?不能应用于Dog and 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");
}
}
答案 0 :(得分:19)
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违反了该规则, 根据其上下文进行分析。使代码正确和有效是非常困难的;它花了我一年的工作中最好的一部分。编译器团队可以更快,更好地完成更多功能,而不需要在不需要的情况下承担这些费用。
答案 1 :(得分:9)
在将分配给Animal a
之前,c和d仍分别为Cat
和Dog
。以下方法按照您的预期方式工作:
Animal a = (Animal)c ?? (Animal)d;
答案 2 :(得分:5)
出于同样的原因Animal a = (true)? d : c;
不起作用(使用三元运算符)。
根据C#规范,表达式的类型推断如下(引用Eric Lippert):
?:运算符的第二个和第三个操作数控制的类型 条件表达式。设X和Y为第二个和第二个的类型 第三个操作数。然后,
- 如果X和Y是相同的类型,则这是条件表达式的类型。
- 否则,如果从X到Y存在隐式转换,但不存在从Y到X的隐式转换,则Y是条件表达式的类型。
- 否则,如果从Y到X存在隐式转换,但不存在从X到Y的隐式转换,则X是条件表达式的类型。
- 否则,无法确定表达式类型,并发生编译时错误。
由于从狗到猫,从猫到狗都没有隐式转换,因此无法推断出类型。 同样的原则适用于空合并运算符。
修改强>
为什么null合并关心猫与狗之间的关系,而不仅仅关注猫与动物,狗与动物之间的关系?
至于为什么编译器不只是意识到两个运算符都是Animal
s:
它只是一大堆蠕虫。我们喜欢这种类型的原则 表达式必须是表达式中某事物的类型。
答案 3 :(得分:2)
失败,因为c
无法隐式转换为d
,反之亦然。 Obvoiously Cat
不是Dog
而Dog
也不是Cat
。
试试这个
Animal a = (Animal)d ?? c;
现在我们说??
左侧操作数的编译器是Animal
,是的,它可以将“dog to animal”转换为??
的右侧和右侧操作数Cat
1}}也可以转换为“动物”。编译器现在很高兴:)