then question operator mark empty diferente c# .net collections null-coalescing-operator

question - inline if null c#



Operador de uniĆ³n nula IList, Array, Enumerable.Empty in foreach (3)

Al usar esta expresión:

a ?? b

Entonces, b debe ser del mismo tipo que a , o debe poder transferirse implícitamente a ese tipo, lo que con referencias significa que debe implementarse o heredarse de cualquier tipo a .

Estos trabajos:

SomethingThatIsIListOfT ?? new T[0] SomethingThatIsIListOfT ?? new T[] { }

Debido a que T[] es un IList<T> , el tipo de matriz implementa esa interfaz.

Sin embargo, esto no funcionará:

SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT

porque el tipo de expresión será a tipo, y el compilador obviamente no puede garantizar que SomethingThatImplementsIEnumerableOfT también implemente IList<T> .

Vas a tener que lanzar uno de los dos lados para que tengas tipos compatibles:

(IEnumerable<T>)SomethingThatIsIListOfT ?? SomethingThatImplementsIEnumerableOfT

Ahora el tipo de expresión es IEnumerable<T> y el ?? El operador puede hacer su cosa.

El "tipo de expresión será el tipo de a " se simplifica un poco, el texto completo de la especificación es el siguiente:

El tipo de expresión a ?? b a ?? b depende de qué conversiones implícitas están disponibles en los operandos. En orden de preferencia, el tipo de a ?? b a ?? b es A0 , A o B , donde A es el tipo de a (siempre que a tenga un tipo), B es el tipo de b (siempre que b tenga un tipo) y A0 es el tipo subyacente de A si A es un tipo anulable, o A contrario. Específicamente, a ?? b a ?? b se procesa de la siguiente manera:

  • Si A existe y no es un tipo anulable o un tipo de referencia, se produce un error en tiempo de compilación.
  • Si b es una expresión dinámica, el tipo de resultado es dinámico. En el tiempo de ejecución, primero se evalúa. Si a no es null , a se convierte en un tipo dinámico, y esto se convierte en el resultado. De lo contrario, se evalúa b , y el resultado se convierte en el resultado.
  • De lo contrario, si A existe y es de tipo anulable y existe una conversión implícita de b a A0 , el tipo de resultado es A0 . En el tiempo de ejecución, primero se evalúa. Si a no es null , a se desenvuelve para escribir A0 y se convierte en el resultado. De lo contrario, b se evalúa y convierte al tipo A0 , y se convierte en el resultado.
  • De lo contrario, si A existe y existe una conversión implícita de b a A , el tipo de resultado es A En el tiempo de ejecución, primero se evalúa. Si a no es nulo, a convierte en el resultado. De lo contrario, b se evalúa y convierte al tipo A , y se convierte en el resultado.
  • De lo contrario, si b tiene un tipo B y existe a conversión implícita de a a B , el tipo de resultado es B En el tiempo de ejecución, primero se evalúa. Si a no es null , a se desenvuelve al tipo A0 (si A existe y es anulable) y se convierte al tipo B , y se convierte en el resultado. De lo contrario, b se evalúa y se convierte en el resultado.
  • De lo contrario, a y b son incompatibles, y se produce un error en tiempo de compilación.

En esta pregunta encontré lo siguiente:

int[] array = null; foreach (int i in array ?? Enumerable.Empty<int>()) { System.Console.WriteLine(string.Format("{0}", i)); }

y

int[] returnArray = Do.Something() ?? new int[] {};

y

... ?? new int[0]

En un NotifyCollectionChangedEventHandler quise aplicar el Enumerable.Empty así:

foreach (DrawingPoint drawingPoint in e.OldItems ?? Enumerable.Empty<DrawingPoint>()) this.RemovePointMarker(drawingPoint);

Nota: OldItems es del tipo IList

Y me da:

Operador ''??'' no se puede aplicar a los operandos de tipo ''System.Collections.IList'' y System.Collections.Generic.IEnumerable<DrawingPoint>

sin embargo

foreach (DrawingPoint drawingPoint in e.OldItems ?? new int[0])

y

foreach (DrawingPoint drawingPoint in e.OldItems ?? new int[] {})

funciona bien

¿Porqué es eso?
¿Por qué IList ?? T[] IList ?? T[] trabajo pero IList ?? IEnumerable<T> IList ?? IEnumerable<T> no?


Creo que determina el tipo de resultado por el primer miembro que en su caso es IList . El primer caso funciona porque una matriz implementa IList . Con IEnumerable no es cierto.

Es solo mi especulación, ya que no hay detalles en la documentación para ?? Operador online .

UPD. Como se señaló en la pregunta aceptada, hay muchos más detalles sobre el tema en la Especificación de C # ( ECMA o en GitHub )


Está utilizando el System.Collections.IList no genérico junto con el System.Collections.Generic.IEnumerable<> genérico System.Collections.Generic.IEnumerable<> , como los operandos del ?? operador. Como ninguna de las interfaces hereda a la otra, eso no funcionará.

Te sugiero que hagas

foreach (DrawingPoint drawingPoint in e.OldItems ?? Array.Empty<DrawingPoint>()) ...

en lugar. Esto funcionará porque cualquier Array es un IList no genérico. (Las matrices unidimensionales de índice cero también son genéricas IList<> al mismo tiempo, por cierto).

El tipo "común" elegido por ?? Será IList no genérico en ese caso.

Array.Empty<T>() tiene la ventaja de reutilizar la misma instancia cada vez que se llama con el mismo tipo de parámetro T

En general, evitaría usar IList no genérico. Tenga en cuenta que existe una DrawingPoint explícita invisible del object a DrawingPoint en el código de foreach que tiene (también con mi sugerencia anterior). Eso es algo que solo se verificará en tiempo de ejecución. Si el IList contiene otros objetos que no son DrawingPoint , explota con una excepción. Si puede usar el IList<> más seguro para el tipo IList<> , entonces los tipos ya se pueden verificar a medida que escribe su código.

Veo un comentario de ckuri (a otra respuesta en el hilo) que ya sugirió Array.Empty<> . Dado que no tiene la versión .NET relevante (de acuerdo con los comentarios allí), tal vez debería hacer algo como:

public static class EmptyArray<TElement> { public static readonly TElement[] Value = new TElement[] { }; }

o solo:

public static class EmptyArray<TElement> { public static readonly TElement[] Value = { }; }

entonces:

foreach (DrawingPoint drawingPoint in e.OldItems ?? EmptyArray<DrawingPoint>.Value) ...

Al igual que con el Array.Empty<>() , esto asegurará que reutilizemos la misma matriz vacía cada vez.

Una sugerencia final es obligar a IList a ser genérico mediante el método de extensión Cast<>() ; entonces puedes usar Enumerable.Empty<>() :

foreach (var drawingPoint in e.OldItems?.Cast<DrawingPoint> ?? Enumerable.Empty<DrawingPoint>() ) ...

Tenga en cuenta el uso de ?. y el hecho de que podemos usar var ahora.