[英]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 ?? b
是A0
, A
或B
,其中A
是a的类型(假设a
具有类型), B
是b
的类型(假设b
具有类型),并且A0
是A
的基础类型,如果A
是可以为空的类型,否则为A
Specifically, a ?? b
具体来说,
a ?? b
a ?? b
is processed as follows: a ?? b
处理如下:
A
exists and is not a nullable type or a reference type, a compile-time error occurs. A
存在且不是可空类型或引用类型,则发生编译时错误。 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. a
不null
, a
被转换为动态类型,这即是结果。 Otherwise, b
is evaluated, and the outcome becomes the result. b
,结果成为结果。 A
exists and is a nullable type and an implicit conversion exists from b
to A0
, the result type is A0
. A
存在且是可空类型,并且存在从b
到A0
的隐式转换,则结果类型为A0
。 At runtime, a
is first evaluated. a
首先计算。 If a
is not null
, a
is unwrapped to type A0
, and it becomes the result. a
不null
, a
解包为类型A0
,这即是结果。 Otherwise, b
is evaluated and converted to type A0
, and it becomes the result. b
被评估并转换为类型A0
,并且它成为结果。 A
exists and an implicit conversion exists from b
to A
, the result type is A
. A
存在且从b
到A
存在隐式转换,则结果类型为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
,它将成为结果。 b
has a type B
and an implicit conversion exists from a
to B
, the result type is B
. b
具有类型B
并且从a
到B
存在隐式转换,则结果类型为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. a
不null
, a
解包为类型A0
(如果A
存在且可为空),并转换为类型B
,并且变得结果。 Otherwise, b
is evaluated and becomes the result. b
被评估并成为结果。 a
and b
are incompatible, and a compile-time error occurs. a
和b
不兼容,并发生编译时错误。 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#规范( ECMA或GitHub )中有关于该主题的更多细节
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
代码中存在从object
到DrawingPoint
的隐形显式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.