この質問 で次のことがわかりました。
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[]
isIList<T>
であるため、配列型はそのインターフェイスを実装します。
ただし、これは機能しません。
SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT
式のタイプはa
タイプであり、コンパイラはSomethingThatImplementsIEnumerableOfT
がIList<T>
も実装していることを保証できないことは明らかです。
互換性のあるタイプになるように、2つの面のいずれかをキャストする必要があります。
(IEnumerable<T>)SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT
これで、式のタイプはIEnumerable<T>
になり、??
演算子で処理できます。
「式のタイプはa
のタイプになります」は少し簡略化されており、仕様の全文は次のとおりです。
式a ?? b
のタイプは、オペランドで使用できる暗黙の変換によって異なります。優先順に、a ?? b
のタイプはA0
、A
、またはB
です。ここで、A
は(a
にタイプがある場合)、B
はb
のタイプ(提供されるb
には型があります)、A0
はA
がnull許容型である場合はA
の基本型であり、それ以外の場合はA
です。具体的には、a ?? b
は次のように処理されます。
A
が存在し、null許容型または参照型ではない場合、コンパイル時エラーが発生します。b
が動的式の場合、結果のタイプは動的です。実行時に、a
が最初に評価されます。 a
がnull
でない場合、a
は動的型に変換され、これが結果になります。それ以外の場合、b
が評価され、結果が結果になります。A
が存在し、それがnull許容型であり、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
が存在し、null可能である場合)、タイプB
に変換され、結果になります。それ以外の場合、b
が評価され、結果になります。a
とb
は互換性がなく、コンパイル時エラーが発生します。_System.Collections.IList
_演算子のオペランドとして、ジェネリック_System.Collections.Generic.IEnumerable<>
_とともにジェネリック_??
_を使用しています。どちらのインターフェースも他方を継承しないため、機能しません。
私はあなたがそうすることを勧めます:
_foreach (DrawingPoint drawingPoint in e.OldItems ?? Array.Empty<DrawingPoint>())
...
_
代わりに。 Array
はジェネリックではないIList
であるため、これは機能します。 (ちなみに、1次元のゼロインデックスの配列はalsoジェネリック_IList<>
_でもあります。)
その場合、_??
_によって選択される「一般的な」タイプは非ジェネリックIList
になります。
Array.Empty<T>()
には、同じ型パラメーターT
で呼び出されるたびに同じインスタンスを再利用できるという利点があります。
一般的に、ジェネリックでないIList
の使用は避けます。あなたが持っているobject
コード内にDrawingPoint
からforeach
への目に見えない明示的なキャストが存在することに注意してください(上記の私の提案も)。これは、実行時にのみチェックされるものです。 IList
にDrawingPoint
以外のオブジェクトが含まれている場合、例外が発生します。よりタイプセーフな_IList<>
_を使用できる場合は、コードを入力するときにすでにタイプをチェックできます。
すでに_Array.Empty<>
_を提案しているckuriによる(スレッド内の別の回答に対する)コメントを見つけました。あなたは関連する.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<>()
メソッドと同様に、毎回同じ空の配列を確実に再利用します。
最後の1つの提案は、IList
をCast<>()
拡張メソッドによってジェネリックにすることを強制することです。次に、Enumerable.Empty<>()
を使用できます。
_foreach (var drawingPoint in
e.OldItems?.Cast<DrawingPoint> ?? Enumerable.Empty<DrawingPoint>()
)
...
_
_?.
_の使用と、var
を使用できることに注意してください。
私はそれがあなたのケースではIListである最初のメンバーによる結果のタイプを決定すると信じています。最初のケースは、配列がIListを実装するため機能します。 IEnumerableの場合、それは真実ではありません。
詳細はないので、これは私の推測にすぎません ??演算子のオンラインドキュメント 。
UPD。受け入れられた質問で指摘されているように、C#仕様( [〜#〜] ecma [〜#〜] または GitHub上 )