简体   繁体   English

Null合并运算符IList,Array,Enumerable.Empty in foreach

[英]Null coalescing operator IList, Array, Enumerable.Empty in foreach

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: NotifyCollectionChangedEventHandler我想像这样应用Enumerable.Empty

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

Note: OldItems is of the type IList 注意: OldItems 属于 IList 类型

And it gives me: 它给了我:

Operator '??' 接线员'??' cannot be applied to operands of type 'System.Collections.IList' and System.Collections.Generic.IEnumerable<DrawingPoint> 不能应用于'System.Collections.IList'类型的操作数和System.Collections.Generic.IEnumerable<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[] 为什么IList ?? T[] IList ?? T[] work but IList ?? IEnumerable<T> IList ?? T[]工作,但IList ?? IEnumerable<T> IList ?? IEnumerable<T> doesn't? IList ?? IEnumerable<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. 然后b要么必须是相同的类型a ,或者它必须隐式地可浇注于该类型,这与参考意味着它必须实现或任何类型继承a是。

These work: 这些工作:

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

because T[] is an IList<T> , the array type implements that interface. 因为T[] IList<T> ,所以数组类型实现了该接口。

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> . 因为表达式的类型将是a类型,并且编译器显然无法保证SomethingThatImplementsIEnumerableOfT也实现了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 ?? 现在表达式的类型是IEnumerable<T>?? 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: 该“类型的表达将是的类型a ”是有点简化,从本说明书的全文如下:


The type of the expression a ?? b 表达式的类型a ?? b a ?? b depends on which implicit conversions are available on the operands. a ?? b取决于操作数上可用的隐式转换。 In order of preference, the type of a ?? b 按优先顺序排列,类型为a ?? b 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. a ?? bA0AB ,其中A是a的类型(假设a具有类型), Bb的类型(假设b具有类型),并且A0A的基础类型,如果A是可以为空的类型,否则为A Specifically, a ?? b 具体来说, a ?? b a ?? b is processed as follows: a ?? b处理如下:

  • If A exists and is not a nullable type or a reference type, a compile-time error occurs. 如果A存在且不是可空类型或引用类型,则发生编译时错误。
  • If b is a dynamic expression, the result type is dynamic. 如果b是动态表达式,则结果类型是动态的。 At runtime, a is first evaluated. 在运行时, a首先计算。 If a is not null , a is converted to a dynamic type, and this becomes the result. 如果anulla被转换为动态类型,这即是结果。 Otherwise, b is evaluated, and the outcome becomes the result. 否则,评估b ,结果成为结果。
  • Otherwise, if A exists and is a nullable type and an implicit conversion exists from b to A0 , the result type is A0 . 否则,如果A存在且是可空类型,并且存在从bA0的隐式转换,则结果类型为A0 At runtime, a is first evaluated. 在运行时, a首先计算。 If a is not null , a is unwrapped to type A0 , and it becomes the result. 如果anulla解包为类型A0 ,这即是结果。 Otherwise, b is evaluated and converted to type A0 , and it becomes the result. 否则, b被评估并转换为类型A0 ,并且它成为结果。
  • Otherwise, if A exists and an implicit conversion exists from b to A , the result type is A . 否则,如果A存在且从bA存在隐式转换,则结果类型为A At runtime, a is first evaluated. 在运行时, a首先计算。 If a is not null, a becomes the result. 如果a不为null,则a成为结果。 Otherwise, b is evaluated and converted to type A , and it becomes the result. 否则,评估b并将其转换为类型A ,它将成为结果。
  • Otherwise, if b has a type B and an implicit conversion exists from a to B , the result type is B . 否则,如果b具有类型B并且从aB存在隐式转换,则结果类型为B At runtime, a is first evaluated. 在运行时, a首先计算。 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. 如果anulla解包为类型A0 (如果A存在且可为空),并转换为类型B ,并且变得结果。 Otherwise, b is evaluated and becomes the result. 否则, b被评估并成为结果。
  • Otherwise, a and b are incompatible, and a compile-time error occurs. 否则, ab不兼容,并发生编译时错误。

I believe that it determines the type of result by the firs member which in your case is IList . 我认为它决定了第一个成员的结果类型,在你的情况下是IList The first case works because an array implements IList . 第一种情况有效,因为数组实现了IList With IEnumerable it's not true. 使用IEnumerable并非如此。

It's just my speculation, as there are no details in the documentation for ?? 这只是我的推测,因为文档中没有详细信息 operator online . 运营商在线

UPD. UPD。 As it pointed out in accepted question, there are a lot more details on the topic in C# Specification ( ECMA or on GitHub ) 正如它在已接受的问题中指出的那样,在C#规范( ECMAGitHub )中有关于该主题的更多细节

You are using the non-generic System.Collections.IList together with the generic System.Collections.Generic.IEnumerable<> , as the operands of the ?? 您正在使用非泛型System.Collections.IList和通用System.Collections.Generic.IEnumerable<> ,作为??的操作数?? operator. 运营商。 Since neither interface inherits the other, that will not work. 由于两个接口都不会继承另一个接口,因此不起作用。

I suggest you do: 我建议你这样做:

foreach (DrawingPoint drawingPoint in e.OldItems ?? Array.Empty<DrawingPoint>())
  ...

instead. 代替。 This will work because any Array is a non-generic IList . 这将起作用,因为任何Array都是非通用的IList (One-dimensional zero-indexed arrays are also generic IList<> at the same time, by the way.) (顺便说一下,一维零索引数组也是通用的IList<> 。)

The "common" type picked by ?? “常见”类型由??挑选?? will be non-generic IList in that case. 在这种情况下,它将是非通用的IList

Array.Empty<T>() has the advantage of reusing the same instance every time it is called with the same type parameter T . Array.Empty<T>()的优点是每次使用相同的类型参数T重用同一实例。

In general, I would avoid using non-generic IList . 一般来说,我会避免使用非通用的IList Note that there exists an invisible explicit cast from object to DrawingPoint in the foreach code you have (also with my suggestion above). 请注意,在您拥有的foreach代码中存在从objectDrawingPoint的隐形显式DrawingPoint转换(也是上面的建议)。 That is something that will only be checked at run-time. 这只是在运行时检查的东西。 If the IList contains other objects than DrawingPoint , it blows up with an exception. 如果IList包含除DrawingPoint之外的其他对象,则会出现异常情况。 If you can use the more type-safe IList<> , then the types can be checked already as you type your code. 如果您可以使用更安全的IList<> ,则可以在键入代码时检查类型。


I see a comment by ckuri (to another answer in the thread) that already suggested Array.Empty<> . 我看到ckuri的评论(在线程中的另一个答案)已经建议Array.Empty<> Since you do not have the relevant .NET version (according to comments there), maybe you should just do something like: 由于你没有相关的.NET版本(根据那里的评论),也许你应该做的事情如下:

public static class EmptyArray<TElement>
{
  public static readonly TElement[] Value = new TElement[] { };
}

or just: 要不就:

public static class EmptyArray<TElement>
{
  public static readonly TElement[] Value = { };
}

then: 然后:

foreach (DrawingPoint drawingPoint in e.OldItems ?? EmptyArray<DrawingPoint>.Value)
  ...

Just like the Array.Empty<>() method, this will ensure we reuse the same empty array each time. 就像Array.Empty<>()方法一样,这将确保我们每次都重用相同的空数组。


One final suggesting is forcing the IList to be generic by the Cast<>() extension method; 最后一个建议是通过Cast<>()扩展方法强制IList是通用的; then you can use Enumerable.Empty<>() : 那么你可以使用Enumerable.Empty<>()

foreach (var drawingPoint in
  e.OldItems?.Cast<DrawingPoint> ?? Enumerable.Empty<DrawingPoint>()
  )
  ...

Note the use of ?. 注意使用?. and the fact that we can use var now. 以及我们现在可以使用var的事实。

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

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