【问题标题】:Null coalescing operator IList, Array, Enumerable.Empty in foreachforeach 中的空值合并运算符 IList、Array、Enumerable.Empty
【发布时间】:2019-02-22 03:41:15
【问题描述】:

this question 我发现了以下内容:

int[] array = null;

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

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

... ?? new int[0]

NotifyCollectionChangedEventHandler 中,我想像这样应用Enumerable.Empty

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

注意: OldItems 属于这种类型 IList

它给了我:

运算符'??'不能应用于“System.Collections.IList”类型的操作数和System.Collections.Generic.IEnumerable&lt;DrawingPoint&gt;

然而

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

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

工作得很好。

为什么会这样?
为什么IList ?? T[] 有效而IList ?? IEnumerable&lt;T&gt; 无效?

【问题讨论】:

  • T[] IList&lt;T&gt;,但不能保证实现IEnumerable&lt;T&gt;的对象实现IList&lt;T&gt;。您必须将 IList&lt;T&gt; 对象转换为 IEnumerable&lt;T&gt; 才能与 ?? Enumerable.Empty&lt;T&gt; 一起使用。

标签: c# .net collections null-coalescing-operator


【解决方案1】:

我相信它决定了第一个成员的结果类型,在你的情况下是 IList。第一种情况有效,因为数组实现了IListIEnumerable 是不正确的。

这只是我的猜测,没有细节in the documentation for ?? operator online

UPD。正如它在接受的问题中指出的那样,在 C# 规范(ECMAon GitHub)中有更多关于该主题的详细信息

【讨论】:

  • C# 规范有明确的细节,我已将其包含在我自己的答案中。
【解决方案2】:

当使用这个表达式时:

a ?? b

那么b 要么必须与a 的类型相同,要么必须隐式转换为该类型,这与引用意味着它必须实现或继承自a 的任何类型。

这些工作:

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

因为T[] 一个IList&lt;T&gt;,数组类型实现了那个接口。

但是,这不起作用:

SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT

因为表达式的类型将是a类型,而编译器显然无法保证SomethingThatImplementsIEnumerableOfT也实现IList&lt;T&gt;

您将不得不强制转换两侧之一,以便您拥有兼容的类型:

(IEnumerable<T>)SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT

现在表达式的类型是IEnumerable&lt;T&gt; 并且?? 运算符可以做它的事情。


“表达式的类型将是a的类型”有点简化,规范的全文如下:


表达式a ?? b 的类型取决于操作数上可用的隐式转换。按照优先顺序,a ?? b 的类型为A0AB,其中A 是a 的类型(前提是a 有类型),Bb 的类型(假设 b 具有类型),如果 A 是可空类型,A0A 的基础类型,否则 A。具体a ?? b处理如下:

  • 如果A 存在且不是可空类型或引用类型,则会发生编译时错误。
  • 如果b 是动态表达式,则结果类型为动态。在运行时,首先评估 a。如果a 不是null,则a 被转换为动态类型,这就是结果。否则,将评估 b,并将结果变为结果。
  • 否则,如果A 存在并且是可空类型并且存在从bA0 的隐式转换,则结果类型为A0。在运行时,首先评估 a。如果a 不是null,则a 被解包为A0,它就变成了结果。否则,b 被求值并转换为类型A0,并成为结果。
  • 否则,如果存在A 并且存在从bA 的隐式转换,则结果类型为A。在运行时,首先评估 a。如果a 不为空,则a 成为结果。否则,b 被求值并转换为类型 A,并成为结果。
  • 否则,如果b 的类型为B,并且存在从aB 的隐式转换,则结果类型为B。在运行时,首先评估 a。如果a 不是null,则a 被解包为A0 类型(如果A 存在并且可以为空)并转换为B 类型,它成为结果。否则,b 被评估并成为结果。
  • 否则ab不兼容,会出现编译时错误。

【讨论】:

  • 谢谢。在.NET Documentation on the Array Class 中可以看到它实现了System.Collections.IListEnumerable.Empty&lt;T&gt; 是否有等效的 IList
  • 除了new T[0],我什么都不知道,当然也有可能是我不知道的。
  • new T[]new List&lt;T&gt;() 都可以解决问题,正如@LasseVågsætherKarlsen 所说
  • @IDarkCoder Array.Empty 通常比 new T[0] 更可取,因为后者总是会构造一个新的数组实例,而 Array.Empty 重复使用相同的实例。
  • 这正是我要找的!但是我仅限于.Net 4.5.2,所以我还不能使用它,因为它的 4.6+ :(
【解决方案3】:

您将非泛型System.Collections.IList 与泛型System.Collections.Generic.IEnumerable&lt;&gt; 一起用作?? 运算符的操作数。由于两个接口都没有继承另一个接口,所以这不起作用。

我建议你这样做:

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

相反。这将起作用,因为任何Array 都是非通用IList。 (顺便说一下,一维零索引数组也是通用IList&lt;&gt;。)

在这种情况下,?? 选择的“通用”类型将是非泛型 IList

Array.Empty&lt;T&gt;() 的优点是每次调用时都使用相同的类型参数T 重复使用相同的实例。

一般来说,我会避免使用非泛型IList。请注意,在您拥有的foreach 代码中存在从objectDrawingPoint 的不可见显式转换(还有我上面的建议)。那是只会在运行时检查的东西。如果IList 包含除DrawingPoint 之外的其他对象,则它会因异常而崩溃。如果您可以使用更安全的IList&lt;&gt;,那么您可以在键入代码时检查类型。


我看到 ckuri 的评论(对线程中的另一个答案)已经建议 Array.Empty&lt;&gt;。由于您没有相关的 .NET 版本(根据那里的 cmets),也许您应该执行以下操作:

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

或者只是:

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

然后:

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

就像Array.Empty&lt;&gt;() 方法一样,这将确保我们每次都重用相同的空数组。


最后一个建议是通过Cast&lt;&gt;() 扩展方法强制IList 通用;那么你可以使用Enumerable.Empty&lt;&gt;():

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

注意?. 的使用以及我们现在可以使用var 的事实。

【讨论】:

  • 我考虑过创建一个单例,但是我认为 int[0] 将工作,直到我们获得 .NET 4.6... 关于IList,我不能 - 不做太多工作进入它在这里不值得 - 更改该集合的类型,因为它是ObservableCollection&lt;T&gt;NotifyCollectionChangedEventHandler 的一部分。虽然我不确定为什么在这种情况下不使用IList&lt;T&gt;,因为该集合是通用的。
猜你喜欢
  • 2018-05-24
  • 2021-08-29
  • 2011-11-08
  • 2021-04-20
  • 2017-09-04
  • 1970-01-01
  • 2017-07-04
  • 2012-09-19
相关资源
最近更新 更多