La conversione dell'array co-variante da x a y può causare un'eccezione di runtime


142

Ho un private readonlyelenco di LinkLabels ( IList<LinkLabel>). Successivamente aggiungo LinkLabels a questo elenco e aggiungo quelle etichette FlowLayoutPanelcome segue:

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

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

ReSharper mi mostra un avvertimento: Co-variant array conversion from LinkLabel[] to Control[] can cause run-time exception on write operation.

Aiutatemi a capire:

  1. Cosa significa questo?
  2. Questo è un controllo utente e non sarà accessibile da più oggetti alle etichette di configurazione, quindi mantenere il codice in quanto tale non influirà su di esso.

Risposte:


154

Ciò significa che è questo

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

E in termini più generali

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

In C #, puoi fare riferimento a una matrice di oggetti (nel tuo caso, LinkLabels) come una matrice di un tipo di base (in questo caso, come una matrice di controlli). È anche tempo di compilazione legale assegnare un altro oggetto che è un Controlarray. Il problema è che l'array non è in realtà un array di controlli. In fase di esecuzione, è ancora un array di LinkLabels. Pertanto, il compito o la scrittura genereranno un'eccezione.


Comprendo la differenza di tempo di runtime / compilazione come nel tuo esempio, ma la conversione da un tipo speciale a un tipo base non è legale? Inoltre ho digitato la lista e vado da LinkLabel(tipo specializzato) a Control(tipo base).
TheVillageIdiot

2
Sì, la conversione da LinkLabel a Control è legale, ma non è la stessa cosa che sta succedendo qui. Questo è un avvertimento sulla conversione da a LinkLabel[]a Control[], che è ancora legale, ma può avere un problema di runtime. Tutto ciò che è cambiato è il modo in cui l'array viene referenziato. L'array stesso non è cambiato. Vedi il problema? L'array è ancora un array del tipo derivato. Il riferimento è tramite una matrice del tipo di base. Pertanto, è tempo di compilazione legale assegnare un elemento del tipo base. Tuttavia il tipo di runtime non lo supporterebbe.
Anthony Pegram,

Nel tuo caso, non penso che sia un problema, stai semplicemente usando l'array per aggiungere a un elenco di controlli.
Anthony Pegram,

6
Se qualcuno si sta chiedendo perché gli array sono erroneamente covarianti in C # ecco la spiegazione di Eric Lippert : è stato aggiunto al CLR perché Java lo richiede e i progettisti del CLR volevano essere in grado di supportare linguaggi simili a Java. Abbiamo quindi aggiunto e aggiunto a C # perché era nel CLR. Questa decisione era alquanto controversa al momento e non ne sono molto contento, ma ora non possiamo farci nulla.
franssu,

14

Proverò a chiarire la risposta di Anthony Pegram.

Il tipo generico è covariante su alcuni argomenti di tipo quando restituisce valori di detto tipo (ad es. Func<out TResult>Restituisce istanze di TResult, IEnumerable<out T>restituisce istanze di T). Cioè, se qualcosa restituisce istanze di TDerived, puoi anche lavorare con tali istanze come se fossero di TBase.

Il tipo generico è contraddittorio su alcuni argomenti di tipo quando accetta valori di detto tipo (ad es. Action<in TArgument>Accetta istanze di TArgument). Cioè, se qualcosa ha bisogno di istanze di TBase, puoi anche passare in istanze di TDerived.

Sembra abbastanza logico che i tipi generici che accettano e restituiscono istanze di un certo tipo (a meno che non sia definito due volte nella firma del tipo generico, ad es. CoolList<TIn, TOut>) Non sono covarianti né contraddittori sull'argomento di tipo corrispondente. Ad esempio, Listè definito in .NET 4 come List<T>, non List<in T>o List<out T>.

Alcuni motivi di compatibilità potrebbero aver fatto sì che Microsoft ignorasse quell'argomento e rendesse gli array covarianti sull'argomento del tipo di valori. Forse hanno condotto un'analisi e hanno scoperto che la maggior parte delle persone utilizza array solo come se fossero di sola lettura (ovvero usano solo inizializzatori di array per scrivere alcuni dati in un array) e, come tali, i vantaggi prevalgono sugli svantaggi causati da un possibile runtime errori quando qualcuno tenterà di utilizzare la covarianza durante la scrittura nell'array. Quindi è permesso ma non incoraggiato.

Per quanto riguarda la tua domanda originale, list.ToArray()crea una nuova LinkLabel[]con i valori copiati dall'elenco originale e, per sbarazzarti di un avviso (ragionevole), dovrai passare Control[]a AddRange. list.ToArray<Control>()farà il lavoro: ToArray<TSource>accetta IEnumerable<TSource>come argomento e restituisce TSource[]; List<LinkLabel>implementa la sola lettura IEnumerable<out LinkLabel>, che, grazie alla IEnumerablecovarianza, potrebbe essere passata al metodo accettando IEnumerable<Control>come argomento.


11

La "soluzione" più semplice

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

Ora, poiché stai cambiando covariante List<LinkLabel>in, IEnumerable<Control>non ci sono più preoccupazioni poiché non è possibile "aggiungere" un elemento a un elenco.


10

L'avvertimento è dovuto al fatto che si potrebbe teoricamente aggiungere un Controldiverso da un LinkLabelal LinkLabel[]attraverso il Control[]riferimento ad esso. Ciò causerebbe un'eccezione di runtime.

La conversione sta avvenendo qui perché AddRangerichiede a Control[].

Più in generale, la conversione di un contenitore di un tipo derivato in un contenitore di un tipo di base è sicura solo se non è possibile modificare successivamente il contenitore nel modo appena descritto. Le matrici non soddisfano tale requisito.


5

La causa principale del problema è descritta correttamente in altre risposte, ma per risolvere l'avviso puoi sempre scrivere:

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

2

Con VS 2008, non ricevo questo avviso. Questo deve essere nuovo per .NET 4.0.
Chiarimento: secondo Sam Mackrill è Resharper che visualizza un avviso.

Il compilatore C # non sa che AddRangenon modificherà l'array passato ad esso. Dato che AddRangeha un parametro di tipo Control[], in teoria potrebbe provare ad assegnare un TextBoxarray, il che sarebbe perfettamente corretto per un array vero di Control, ma l'array è in realtà un array di LinkLabelse non accetterà tale assegnazione.

Realizzare la codifica di array in c # è stata una cattiva decisione di Microsoft. Sebbene possa sembrare una buona idea poter assegnare una matrice di un tipo derivato a una matrice di un tipo di base in primo luogo, ciò può portare a errori di runtime!


2
Ricevo questo avviso da
Resharper

1

Cosa ne pensi di questo?

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

2
Stesso risultato di _list.ToArray<Control>().
jsuddsjr,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.