Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

null coalescing issue with abstract base/derived classes

Why is the C# null coalescing operator not able to figure this out?

  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

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");
  }
}
like image 343
user1054637 Avatar asked Nov 14 '13 10:11

user1054637


People also ask

Can I use null coalescing operator?

Null-coalescing Operator Use this operator to fall back on a given value. In cases where a statement could return null, the null-coalescing operator can be used to ensure a reasonable value gets returned. This code returns the name of an item or the default name if the item is null.

Why we use null coalescing operator?

The null-coalescing operator ?? returns the value of its left-hand operand if it isn't null ; otherwise, it evaluates the right-hand operand and returns its result.

What is nullable types and null coalescing operator in C#?

Nullable types work as a connector between a database and C# code to provide a way to transform the nulls to be used in C# code. Null Coalescing operators simplify the way to check for nulls and shorten the C# code when the developer is dealing with nulls.

Which is null coalescing operator?

The nullish coalescing operator ( ?? ) is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined , and otherwise returns its left-hand side operand.


2 Answers

Before assigning it to Animal a, c and d are still Cat and Dog respectively. The following does work the way you'd expect:

Animal a = (Animal)c ?? (Animal)d;
like image 21
C.Evenhuis Avatar answered Nov 04 '22 04:11

C.Evenhuis


One of the subtle design rules of C# is that C# never infers a type that wasn't in the expression to begin with. Since Animal is not in the expression d ?? c, the type Animal is not a choice.

This principle applies everywhere that C# infers types. 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 ?? 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. But what about:

var x = a ?? b;

Now the type of x is being inferred. 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. 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".

That rule is violated for lambdas, which are analyzed based on their context. 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.

like image 58
Eric Lippert Avatar answered Nov 04 '22 05:11

Eric Lippert