Null coalescing operator IList, Array, Enumerable.Empty in foreach

  • A+
Category:Languages

In this question I found the following:

int[] array = null;  foreach (int i in array ?? Enumerable.Empty<int>())   {       System.Console.WriteLine(string.Format("{0}", i));   }   

and

int[] returnArray = Do.Something() ?? new int[] {}; 

and

... ?? new int[0] 

In a NotifyCollectionChangedEventHandler I wanted to apply the Enumerable.Empty like so:

foreach (DrawingPoint drawingPoint in e.OldItems ?? Enumerable.Empty<DrawingPoint>())     this.RemovePointMarker(drawingPoint); 

Note: OldItems is of the type IList

And it gives me:

Operator '??' cannot be applied to operands of type 'System.Collections.IList' and System.Collections.Generic.IEnumerable<WWSmartAufmassWpf.Data.DrawingPoint>

However

foreach (DrawingPoint drawingPoint in e.OldItems ?? new int[0]) 

and

foreach (DrawingPoint drawingPoint in e.OldItems ?? new int[] {}) 

works just fine.

Why is that?
Why does IList ?? T[] work but IList ?? IEnumerable<T> doesn't?

 


When using this expression:

a ?? b 

Then b either must be the same type as a, or it must be implicitly castable to that type, which with references means that it has to implement or inherit from whatever type a is.

These work:

SomethingThatIsIListOfT ?? new T[0] SomethingThatIsIListOfT ?? new T[] { } 

because T[] is an IList<T>, the array type implements that interface.

However, this won't work:

SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT 

because the type of the expression will be the a type, and the compiler is obviously unable to guarantee that SomethingThatImplementsIEnumerableOfT also implements IList<T>.

You're going to have to cast one of the two sides so that you have compatible types:

(IEnumerable<T>)SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT 

Now the type of the expression is IEnumerable<T> and the ?? operator can do its thing.


The "type of the expression will be the type of a" is a bit simplified, the full text from the specification is as follows:


The type of the expression a ?? b depends on which implicit conversions are available on the operands. In order of preference, the type of a ?? b is A0, A, or B, where A is the type of a (provided that a has a type), B is the type of b (provided that b has a type), and A0 is the underlying type of A if A is a nullable type, or A otherwise. Specifically, a ?? b is processed as follows:

  • If A exists and is not a nullable type or a reference type, a compile-time error occurs.
  • If b is a dynamic expression, the result type is dynamic. At runtime, a is first evaluated. If a is not null, a is converted to a dynamic type, and this becomes the result. Otherwise, b is evaluated, and the outcome becomes the result.
  • Otherwise, if A exists and is a nullable type and an implicit conversion exists from b to A0, the result type is A0. At runtime, a is first evaluated. If a is not null, a is unwrapped to type A0, and it becomes the result. Otherwise, b is evaluated and converted to type A0, and it becomes the result.
  • Otherwise, if A exists and an implicit conversion exists from b to A, the result type is A. At runtime, a is first evaluated. If a is not null, a becomes the result. Otherwise, b is evaluated and converted to type A, and it becomes the result.
  • Otherwise, if b has a type B and an implicit conversion exists from a to B, the result type is B. At runtime, a is first evaluated. If a is not null, a is unwrapped to type A0 (if A exists and is nullable) and converted to type B, and it becomes the result. Otherwise, b is evaluated and becomes the result.
  • Otherwise, a and b are incompatible, and a compile-time error occurs.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: