简体   繁体   English

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

[英]null coalescing issue with abstract base/derived classes

Why is the C# null coalescing operator not able to figure this out? 为什么C#null合并运算符无法解决这个问题?

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

  Animal a = d ?? c;

This will give the error 这将给出错误

Operator ?? 操作员? cannot be applied to operands of type Dog and Cat 不能应用于Dog和Cat类型的操作数

It just seems strange given the following compiles. 鉴于以下编译,这看起来很奇怪。

Animal a = d;
a = c;

Contextual code below: 下面的上下文代码:

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

One of the subtle design rules of C# is that C# never infers a type that wasn't in the expression to begin with. C#的一个微妙的设计规则是C#永远不会推断出一个不在表达式中的类型。 Since Animal is not in the expression d ?? c 既然Animal不在表达式中d ?? c d ?? c , the type Animal is not a choice. d ?? c ,类型Animal不是一个选择。

This principle applies everywhere that C# infers types. 这个原则适用于C#推断类型的所有地方。 For example: 例如:

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

The compiler does not say "this must be an array of animals", it says "I think you made a mistake". 编译器没有说“这必须是一群动物”,它说“我认为你犯了一个错误”。

This is then a specific version of the more general design rule, which is "give an error when a program looks ambiguous rather than making a guess that might be wrong". 这是更一般的设计规则的特定版本,“当程序看起来不明确而不是做出可能错误的猜测时给出错误”。

Another design rule that comes into play here is: reason about types from inside to outside, not from outside to inside. 这里发挥作用的另一个设计规则是:从内到外的类型的原因,而不是从外到内的类型。 That is, you should be able to work out the type of everything in an expression by looking at its parts , without looking at its context . 也就是说,您应该能够通过查看其中的部分来计算表达式中所有内容的类型,而无需查看其上下文 In your example, Animal comes from outside the ?? 在你的例子中, Animal来自外面?? expression; 表达; we should be able to figure out what the type of the ?? 我们应该能够弄清楚什么类型的?? expression is and then ask the question "is this type compatible with the context?" 表达式然后问问题“这种类型是否与上下文兼容?” rather than going the other way and saying "here's the context -- now work out the type of the ?? expression." 而不是走另一条路并说“这里是上下文 - 现在解决了??表达式的类型。”

This rule is justified because very often the context is unclear. 这条规则是合理的,因为上下文不清楚。 In your case the context is very clear; 在你的情况下,背景非常清楚; the thing is being assigned to Animal . 事情被分配给Animal But what about: 但是关于:

var x = a ?? b;

Now the type of x is being inferred. 现在推断出x的类型。 We don't know the type of the context because that's what we're working out. 我们不知道上下文的类型,因为这就是我们正在研究的内容。 Or 要么

M(a ?? b)

There might be two dozen overloads of M and we need to know which one to pick based on the type of the argument. M可能有二十多个重载,我们需要根据参数的类型知道要选择哪一个。 It is very hard to reason the other way and say "the context could be one of these dozen things; evaluate a??b in each context and work out its type". 很难用另一种方式来推理并说“上下文可能是这些事情中的一个;在每个上下文中评估a??b并计算出它的类型”。

That rule is violated for lambdas, which are analyzed based on their context. 这条规则被违反了lambda表达式,这基于它们的上下文进行分析。 Getting that code both correct and efficient was very difficult; 使代码正确和有效是非常困难的; it took me the better part of a year's work. 它花了我一年的工作中最好的一部分。 The compiler team can do more features, faster and better, by not taking on that expense where it is not needed. 编译器团队可以更快,更好地完成更多功能,而不需要在不需要的情况下承担这些费用。

Before assigning it to Animal a , c and d are still Cat and Dog respectively. 分配Animal a之前,c和d仍然分别是CatDog The following does work the way you'd expect: 以下工作方式符合您的预期:

Animal a = (Animal)c ?? (Animal)d;

For the same reason that Animal a = (true)? d : c; 出于同样的原因, Animal a = (true)? d : c; Animal a = (true)? d : c; won't work (using the ternary operator). 不起作用(使用三元运算符)。

According to the C# specification, the type of the expression is inferred as follows (quoting Eric Lippert ): 根据C#规范,表达式的类型推断如下(引用Eric Lippert ):

The second and third operands of the ?: operator control the type of the conditional expression. ?:运算符的第二个和第三个操作数控制条件表达式的类型。 Let X and Y be the types of the second and third operands. 设X和Y是第二个和第三个操作数的类型。 Then, 然后,

  • If X and Y are the same type, then this is the type of the conditional expression. 如果X和Y是相同的类型,那么这是条件表达式的类型。
  • Otherwise, if an implicit conversion exists from X to Y, but not from Y to X, then Y is the type of the conditional expression. 否则,如果从X到Y存在隐式转换,而不是从Y到X,则Y是条件表达式的类型。
  • Otherwise, if an implicit conversion exists from Y to X, but not from X to Y, then X is the type of the conditional expression. 否则,如果存在从Y到X的隐式转换,但不存在从X到Y的隐式转换,则X是条件表达式的类型。
  • Otherwise, no expression type can be determined, and a compile-time error occurs. 否则,无法确定表达式类型,并发生编译时错误。

Since no implicit conversion exists from Dog to Cat, nor from Cat to Dog, then the type can't be inferred. 由于从狗到猫,从猫到狗都没有隐式转换,因此无法推断出类型。 The same principle applies to the null coalescing operator. 同样的原则适用于空合并运算符。

Edit 编辑

Why does null coalesce care about the relationship between Cat and Dog and not just about the relationship between Cat and Animal and Dog and Animal? 为什么null合并关心猫与狗之间的关系,而不仅仅关注猫与动物,狗与动物之间的关系?

As to why the compiler doesn't just realize that both operators are Animal s: 至于为什么编译器不只是意识到两个运算符都是Animal s:

It's just too big a can of worms. 它只是一大堆蠕虫。 We like the principle that the type of the expression must be the type of something in the expression. 我们喜欢表达式的类型必须是表达式中某种类型的原则。

It fails because c cannot be converted to d implicitly and vice-versa. 它失败,因为c无法隐式转换为d ,反之亦然。 Obvoiously Cat is not a Dog and Dog is not a Cat either. 贪得无厌的Cat不是DogDog也不是Cat

Try this 试试这个

Animal a = (Animal)d ?? c;

Now we say the compiler that left hand side operand of ?? 现在我们说编译器左手边的操作数?? is Animal and yes it can convert "dog to animal" and also right and side operand of ?? Animal和是的,它可以转换成“狗的动物”,也有权利和侧面操作?? is Cat that is also can be converted to "Animal". Cat ,也可以转换为“动物”。 Compiler is happy now :) 编译器很高兴:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM