在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<DrawingPoint>
类型的操作数
然而
foreach (DrawingPoint drawingPoint in e.OldItems ?? new int[0])
和
foreach (DrawingPoint drawingPoint in e.OldItems ?? new int[] {})
工作得很好。
这是为什么?
为什么IList ?? T[]
工作,但IList ?? IEnumerable<T>
不工作?
使用此表达式时:
a ?? b
然后b
必须与a
的类型相同,或者它必须是隐式可转换为该类型,带引用意味着它必须实现或继承a
的任何类型。
这些工作:
SomethingThatIsIListOfT ?? new T[0]
SomethingThatIsIListOfT ?? new T[] { }
因为T[]
是IList<T>
,所以数组类型实现了该接口。
但是,这不起作用:
SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT
因为表达式的类型将是a
类型,并且编译器显然无法保证SomethingThatImplementsIEnumerableOfT
也实现IList<T>
。
你将不得不施放两个中的一个,以便你有兼容的类型:
(IEnumerable<T>)SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT
现在表达式的类型是IEnumerable<T>
,而??
运算符可以做它的事情。
“表达式的类型将是a
的类型”有点简化,规范中的全文如下:
表达式a ?? b
的类型取决于操作数上可用的隐式转换。按照优先顺序,a ?? b
的类型是A0
,A
或B
,其中A
是a的类型(假设a
有类型),B
是b
的类型(假设b
有类型),和A0
如果A
是可以为空的类型,则为A
的基础类型,否则为A
。具体来说,a ?? b
处理如下:
A
存在且不是可空类型或引用类型,则会发生编译时错误。b
是动态表达式,则结果类型是动态的。在运行时,首先评估a
。如果a
不是null
,则a
将转换为动态类型,这就成了结果。否则,评估b
,结果成为结果。A
存在并且是可空类型并且存在从b
到A0
的隐式转换,则结果类型为A0
。在运行时,首先评估a
。如果a
不是null
,a
将被打开以打字A0
,它就会成为结果。否则,评估b
并将其转换为A0
类型,它就会成为结果。A
并且存在从b
到A
的隐式转换,则结果类型为A
。在运行时,首先评估a
。如果a
不为null,则a
成为结果。否则,评估b
并将其转换为A
类型,它就会成为结果。b
的类型为B
,并且存在从a
到B
的隐式转换,则结果类型为B
。在运行时,首先评估a
。如果a
不是null
,则a
打开以打包A0
(如果A
存在且可以为空)并转换为B
类型,它就会成为结果。否则,评估b
并成为结果。a
和b
不兼容,并发生编译时错误。我认为它决定了第一个成员的结果类型,在你的情况下是IList。第一种情况有效,因为数组实现了IList。使用IEnumerable并非如此。
这只是我的推测,因为没有细节in the documentation for ?? operator online。
您使用非泛型System.Collections.IList
和通用System.Collections.Generic.IEnumerable<>
作为??
运算符的操作数。由于两个接口都不会继承另一个接口,因此不起作用。
我建议你这样做:
foreach (DrawingPoint drawingPoint in e.OldItems ?? Array.Empty<DrawingPoint>())
...
代替。这将有效,因为任何Array
都是非通用的IList
。 (顺便说一下,一维零索引数组也是通用的IList<>
。)
在这种情况下,??
选择的“普通”类型将是非通用的IList
。
Array.Empty<T>()
具有每次使用相同类型参数T
调用时重用相同实例的优势。
一般来说,我会避免使用非通用的IList
。请注意,在object
代码中存在从DrawingPoint
到foreach
的隐形显式转换(也是我的建议)。这只是在运行时检查的东西。如果IList
包含除DrawingPoint
之外的其他对象,则会出现异常情况。如果您可以使用更安全的IList<>
,则可以在键入代码时检查类型。
我看到ckuri的评论(在线程中的另一个答案)已经建议Array.Empty<>
。由于你没有相关的.NET版本(根据那里的评论),也许你应该做的事情如下:
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<>()
方法一样,这将确保我们每次都重用相同的空数组。
最后一个建议是通过IList
扩展方法强制Cast<>()
是通用的;那么你可以使用Enumerable.Empty<>()
:
foreach (var drawingPoint in
e.OldItems?.Cast<DrawingPoint> ?? Enumerable.Empty<DrawingPoint>()
)
...
请注意使用?.
以及我们现在可以使用var
的事实。