La conversion d’un tableau co-variant de x à y peut provoquer une exception à l’exécution

J’ai une liste private readonly de LinkLabel ( IList ). J’ajoute plus tard les LinkLabel s à cette liste et ajoute ces étiquettes à un FlowLayoutPanel comme suit:

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

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

S’il vous plaît aidez-moi à comprendre:

  1. Que cela veut-il dire?
  2. Il s’agit d’un contrôle utilisateur auquel de nombreux objects n’accèdent pas pour configurer les étiquettes, de sorte que conserver le code en tant que tel ne l’affectera pas.

Qu’est-ce que cela signifie est-ce

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

Et en termes plus généraux

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

En C #, vous êtes autorisé à référencer un tableau d’objects (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 temps de comstackr pour atsortingbuer un autre object qui est un Control au tableau. Le problème est que le tableau n’est pas un tableau de contrôles. A l’exécution, il s’agit toujours d’un tableau de LinkLabels. En tant que tel, l’affectation, ou écriture, lancera une exception.

Je vais essayer de clarifier la réponse d’Anthony Pegram.

Le type générique est covariant sur un argument de type lorsqu’il renvoie des valeurs dudit type (par exemple, Func renvoie des instances de TResult , IEnumerable renvoie des instances de T ). En d’autres TDerived , si quelque chose renvoie des instances de TDerived , vous pouvez aussi TDerived des instances telles que celles de TBase .

Le type générique est contravariant sur certains arguments de type lorsqu’il accepte des valeurs de ce type (par exemple, Action accepte les instances de TArgument ). C’est-à-dire que 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 retournent des instances d’un certain type (à moins que ce ne soit deux fois défini dans la signature de type générique, par exemple CoolList ) ne soient ni covariants ni contravariants dans l’argument de type correspondant. Par exemple, List est défini dans .NET 4 comme List , pas List ou List .

Certaines raisons de compatibilité ont pu amener Microsoft à ignorer cet argument et à rendre les tableaux covariants sur leur argument de type valeurs. Peut-être ont-ils procédé à une parsing et constaté que la plupart des utilisateurs n’utilisent les tableaux que s’ils étaient en lecture seule (c’est-à-dire qu’ils n’utilisent que des initialiseurs de tableaux pour écrire des données dans un tableau). des erreurs lorsque quelqu’un essaiera d’utiliser la covariance lors de l’écriture dans le tableau. Il est donc permis mais non encouragé.

En ce qui concerne votre question d’origine, list.ToArray() crée un nouveau LinkLabel[] avec les valeurs copiées de la liste d’origine et, pour se débarrasser des avertissements (raisonnables), vous devrez passer Control[] à AddRange . list.ToArray() fera le travail: ToArray accepte IEnumerable comme argument et retourne TSource[] ; List implémente en lecture seule IEnumerable , qui, grâce à la covariance IEnumerable , pourrait être transmise à la méthode acceptant l’argument IEnumerable .

L’avertissement est dû au fait que vous pouvez théoriquement append un Control autre qu’un LinkLabel au LinkLabel[] via la référence Control[] . Cela provoquerait une exception d’exécution.

La conversion se produit ici car 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 le conteneur par la suite. Les tableaux ne satisfont pas à cette exigence.

La “solution” la plus simple

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

Maintenant que vous modifiez de manière covariante List en IEnumerable il n’y a plus de soucis car il n’est pas possible “d’append” un élément à un énumérable.

La cause première du problème est correctement décrite dans d’autres réponses, mais pour résoudre cet 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 passé. Comme AddRange a un paramètre de type Control[] , il pourrait en théorie essayer d’atsortingbuer 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 cession.

Rendre les tableaux co-variant dans c # était une mauvaise décision de Microsoft. Bien que cela puisse sembler une bonne idée de pouvoir atsortingbuer 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().ToArray());