Нулевой оператор объединения IList, Array, Enumerable.Empty в foreach - PullRequest
0 голосов
/ 18 сентября 2018

В этот вопрос Я нашел следующее:

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> не работает?

Ответы [ 3 ]

0 голосов
/ 18 сентября 2018

При использовании этого выражения:

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 зависит от того, какие последствияT преобразования доступны на операндах.В порядке предпочтения тип 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 существует и имеет тип NULL, а неявное преобразование существует из 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 оценивается и становится результатом.
  • В противном случае a и b несовместимы, и возникает ошибка времени компиляции.
0 голосов
/ 18 сентября 2018

Вы используете неуниверсальный 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 сейчас.

0 голосов
/ 18 сентября 2018

Я полагаю, что он определяет тип результата для первого члена, который в вашем случае равен IList . Первый случай работает, потому что массив реализует IList . С IEnumerable это не так.

Это просто мое предположение, так как в документации для * нет подробностей ?? Оператор онлайн .

UPD. Как уже указывалось в принятом вопросе, в спецификации C # есть намного больше деталей по теме ( ECMA или на GitHub )

...