c# winforms .net-4.0 covariance

c# - La conversión de matriz covariante de x a y puede causar una excepción en tiempo de ejecución



winforms .net-4.0 (7)

¿Qué tal esto?

flPanel.Controls.AddRange(_list.OfType<Control>().ToArray());

Tengo una lista private readonly de LinkLabel s ( IList<LinkLabel> ). Más tarde LinkLabel s a esta lista y LinkLabel esas etiquetas a un FlowLayoutPanel siguiente manera:

foreach(var s in strings) { _list.Add(new LinkLabel{Text=s}); } flPanel.Controls.AddRange(_list.ToArray());

Resharper me muestra una advertencia: la Co-variant array conversion from LinkLabel[] to Control[] can cause run-time exception on write operation .

Por favor, ayúdame a descubrir:

  1. ¿Qué significa esto?
  2. Este es un control de usuario y no se accederá a múltiples objetos para configurar las etiquetas, por lo que mantener el código como tal no lo afectará.

Con VS 2008, no recibo esta advertencia. Esto debe ser nuevo para .NET 4.0.
Aclaración: de acuerdo con Sam Mackrill es Resharper quien muestra una advertencia.

El compilador de C # no sabe que AddRange no modificará la matriz que se le pasó. Como AddRange tiene un parámetro de tipo Control[] , en teoría podría intentar asignar un TextBox a la matriz, lo que sería perfectamente correcto para una verdadera matriz de Control , pero la matriz es en realidad una matriz de LinkLabels de LinkLabels y no aceptará tal asignación.

Hacer matrices variadas en c # fue una mala decisión de Microsoft. Si bien puede parecer una buena idea poder asignar una matriz de un tipo derivado a una matriz de un tipo base, en primer lugar, ¡esto puede generar errores de tiempo de ejecución!


La "solución" más directa

flPanel.Controls.AddRange(_list.AsEnumerable());

Ahora que está cambiando de forma List<LinkLabel> a IEnumerable<Control> no hay más preocupaciones ya que no es posible "agregar" un elemento a un enumerable.


La advertencia se debe al hecho de que en teoría podría agregar un Control distinto de un LinkLabel de LinkLabel a la LinkLabel[] través de la referencia de Control[] . Esto causaría una excepción de tiempo de ejecución.

La conversión está ocurriendo aquí porque AddRange toma un Control[] .

De manera más general, la conversión de un contenedor de un tipo derivado a un contenedor de un tipo base solo es seguro si no puede modificar posteriormente el contenedor de la forma que se acaba de describir. Las matrices no satisfacen ese requisito.


La causa raíz del problema se describe correctamente en otras respuestas, pero para resolver la advertencia, siempre puede escribir:

_list.ForEach(lnkLbl => flPanel.Controls.Add(lnkLbl));


Lo que significa es esto

Control[] controls = new LinkLabel[10]; // compile time legal controls[0] = new TextBox(); // compile time legal, runtime exception

Y en términos más generales

string[] array = new string[10]; object[] objs = array; // legal at compile time objs[0] = new Foo(); // again legal, with runtime exception

En C #, se le permite hacer referencia a una matriz de objetos (en su caso, LinkLabels) como una matriz de un tipo base (en este caso, como una matriz de Controles). También es legal en tiempo de compilación asignar otro objeto que sea un Control a la matriz. El problema es que la matriz no es en realidad una matriz de Controles. En tiempo de ejecución, todavía es una matriz de etiquetas de enlace. Como tal, la asignación, o escritura, lanzará una excepción.


Trataré de aclarar la respuesta de Anthony Pegram.

El tipo genérico es covariante en algún argumento de tipo cuando devuelve valores de dicho tipo (por ejemplo, Func<out TResult> devuelve instancias de TResult , IEnumerable<out T> devuelve instancias de T ). Es decir, si algo devuelve instancias de TDerived , también puede trabajar con instancias como si fueran de TBase .

El tipo genérico es contravariante en algún tipo de argumento cuando acepta valores de dicho tipo (por ejemplo, Action<in TArgument> acepta instancias de TArgument ). Es decir, si algo necesita instancias de TBase , también puede pasar instancias de TDerived .

Parece bastante lógico que los tipos genéricos que aceptan y devuelven instancias de algún tipo (a menos que se defina dos veces en la firma de tipo genérico, p. Ej. CoolList<TIn, TOut> ) no sean covariantes ni contravariantes en el argumento de tipo correspondiente. Por ejemplo, List se define en .NET 4 como List<T> , no List<in T> o List<out T> .

Algunas razones de compatibilidad pueden haber causado que Microsoft ignore ese argumento y haga que las matrices sean covariantes en su argumento de tipo de valores. Tal vez llevaron a cabo un análisis y descubrieron que la mayoría de las personas solo utiliza matrices como si fueran de solo lectura (es decir, solo usan inicializadores de matriz para escribir algunos datos en una matriz) y, como tal, las ventajas superan las desventajas causadas por el posible tiempo de ejecución errores cuando alguien intentará usar la covarianza al escribir en la matriz. Por lo tanto, está permitido pero no fomentado.

En cuanto a su pregunta original, list.ToArray() crea una nueva LinkLabel[] con los valores copiados de la lista original y, para deshacerse de la advertencia (razonable), deberá pasar Control[] a AddRange . list.ToArray<Control>() hará el trabajo: ToArray<TSource> acepta IEnumerable<TSource> como su argumento y devuelve TSource[] ; List<LinkLabel> implementa IEnumerable<out LinkLabel> solo IEnumerable<out LinkLabel> , que, gracias a la covarianza de IEnumerable , podría pasar al método aceptando IEnumerable<Control> como su argumento.