在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>
无效?
答案 0 :(得分:20)
使用此表达式时:
a ?? b
然后,b
必须与a
相同,或者必须隐式转换为该类型,这意味着引用必须实现或继承{{1} }是。
这些作品:
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的类型(前提是{{1 }}具有类型),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
进行求值并将其转换为类型a
,它将成为结果。b
具有类型A
,并且存在从b
到B
的隐式转换,则结果类型为a
。在运行时,首先评估B
。如果B
不是a
,则将a
展开为类型null
(如果a
存在并且可以为空)并转换为类型A0
,它成为结果。否则,将评估A
并成为结果。B
和b
不兼容,并且发生编译时错误。答案 1 :(得分:2)
我相信它决定了第一位成员(在您的情况下为
这只是我的推测,因为in the documentation for ?? operator online没有详细信息。
答案 2 :(得分:2)
您正在将非通用System.Collections.IList
与通用System.Collections.Generic.IEnumerable<>
一起用作??
运算符的操作数。由于两个接口都不继承另一个接口,因此将无法正常工作。
我建议你这样做
foreach (DrawingPoint drawingPoint in e.OldItems ?? Array.Empty<DrawingPoint>())
...
相反。这将起作用,因为任何Array
都是非通用的IList
。 (顺便说一下,一维零索引数组同时也是 通用IList<>
。)
在这种情况下,由??
选择的“ common”类型将是非通用的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
的事实。