J'ai un private readonly
liste des LinkLabel
s (IList<LinkLabel>
). J'ajoute plus tard LinkLabel
s à cette liste et j'ajoute ces étiquettes à un FlowLayoutPanel
comme suit:
foreach(var s in strings)
{
_list.Add(new LinkLabel{Text=s});
}
flPanel.Controls.AddRange(_list.ToArray());
Resharper me montre un avertissement: Co-variant array conversion from LinkLabel[] to Control[] can cause run-time exception on write operation
.
Aidez-moi à comprendre:
Qu'est-ce que cela signifie
Control[] controls = new LinkLabel[10]; // compile time legal
controls[0] = new TextBox(); // compile time legal, runtime exception
Et en termes plus généraux
string[] array = new string[10];
object[] objs = array; // legal at compile time
objs[0] = new Foo(); // again legal, with runtime exception
En C #, vous êtes autorisé à référencer un tableau d'objets (dans votre cas, LinkLabels) en tant que tableau d'un type de base (dans ce cas, en tant que tableau de contrôles). Il est également légal pour la compilation d'affecter n autre objet qui est un Control
au tableau. Le problème est que le tableau n'est pas réellement un tableau de contrôles. Au moment de l'exécution, il s'agit toujours d'un tableau de LinkLabels. En tant que tel, l'affectation ou l'écriture lèvera une exception.
Je vais essayer de clarifier la réponse d'Anthony Pegram.
Le type générique est covariant sur certains arguments de type lorsqu'il renvoie des valeurs dudit type (par exemple Func<out TResult>
Renvoie des instances de TResult
, IEnumerable<out T>
Renvoie des instances de T
). Autrement dit, si quelque chose renvoie des instances de TDerived
, vous pouvez aussi bien travailler avec de telles instances que si elles étaient de TBase
.
Le type générique est contraire à certains arguments de type lorsqu'il accepte des valeurs dudit type (par exemple, Action<in TArgument>
Accepte des instances de TArgument
). Autrement dit, si quelque chose a besoin d'instances de TBase
, vous pouvez également passer des instances de TDerived
.
Il semble tout à fait logique que les types génériques qui acceptent et renvoient des instances d'un certain type (à moins qu'il ne soit défini deux fois dans la signature de type générique, par exemple CoolList<TIn, TOut>
) Ne sont pas covariants ni contravariants sur l'argument de type correspondant. Par exemple, List
est défini dans .NET 4 comme List<T>
, Pas List<in T>
Ou List<out T>
.
Certaines raisons de compatibilité peuvent avoir amené Microsoft à ignorer cet argument et à rendre les tableaux covariants sur leur argument de type de valeurs. Peut-être qu'ils ont effectué une analyse et constaté que la plupart des gens n'utilisent que des tableaux comme s'ils étaient en lecture seule (c'est-à-dire qu'ils n'utilisent que des initialiseurs de tableau pour écrire des données dans un tableau), et, en tant que tels, les avantages l'emportent sur les inconvénients causés par une exécution possible des erreurs lorsque quelqu'un essaiera d'utiliser la covariance lors de l'écriture dans le tableau. Par conséquent, il est permis mais pas encouragé.
Quant à votre question d'origine, list.ToArray()
crée un nouveau LinkLabel[]
Avec des valeurs copiées à partir de la liste d'origine et, pour vous débarrasser de l'avertissement (raisonnable), vous devrez passer Control[]
À AddRange
. list.ToArray<Control>()
fera le travail: ToArray<TSource>
accepte IEnumerable<TSource>
comme argument et retourne TSource[]
; List<LinkLabel>
Implémente en lecture seule IEnumerable<out LinkLabel>
Qui, grâce à la covariance IEnumerable
, peut être passé à la méthode acceptant IEnumerable<Control>
Comme argument.
La "solution" la plus simple
flPanel.Controls.AddRange(_list.AsEnumerable());
Maintenant que vous changez de manière covariante List<LinkLabel>
à IEnumerable<Control>
il n'y a plus de soucis car il n'est pas possible "d'ajouter" un élément à un énumérable.
L'avertissement est dû au fait que vous pourriez théoriquement ajouter un Control
autre qu'un LinkLabel
au LinkLabel[]
à travers le Control[]
référence à celui-ci. Cela provoquerait une exception d'exécution.
La conversion se produit ici parce que AddRange
prend un Control[]
.
Plus généralement, la conversion d'un conteneur d'un type dérivé en conteneur d'un type de base n'est sûre que si vous ne pouvez pas modifier ultérieurement le conteneur de la manière décrite ci-dessus. Les tableaux ne satisfont pas à cette exigence.
La cause première du problème est correctement décrite dans d'autres réponses, mais pour résoudre l'avertissement, vous pouvez toujours écrire:
_list.ForEach(lnkLbl => flPanel.Controls.Add(lnkLbl));
Avec VS 2008, je ne reçois pas cet avertissement. Cela doit être nouveau pour .NET 4.0.
Précision: selon Sam Mackrill, c'est Resharper qui affiche un avertissement.
Le compilateur C # ne sait pas que AddRange
ne modifiera pas le tableau qui lui est transmis. Puisque AddRange
a un paramètre de type Control[]
, il pourrait en théorie essayer d'assigner un TextBox
au tableau, ce qui serait parfaitement correct pour un vrai tableau de Control
, mais le tableau est en réalité un tableau de LinkLabels
et n'acceptera pas une telle affectation.
Faire des tableaux co-variant en c # était une mauvaise décision de Microsoft. Bien que cela puisse sembler une bonne idée de pouvoir affecter un tableau d'un type dérivé à un tableau d'un type de base en premier lieu, cela peut entraîner des erreurs d'exécution!
Que dis-tu de ça?
flPanel.Controls.AddRange(_list.OfType<Control>().ToArray());