149 votes

La conversion de tableaux covariants de x en y peut provoquer une exception d'exécution.

J'ai un private readonly liste de LinkLabel s ( IList<LinkLabel> ). J'ajoute ensuite LinkLabel à cette liste et ajoutez ces étiquettes à un fichier 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 .

Veuillez m'aider à comprendre :

  1. Qu'est-ce que cela signifie ?
  2. Il s'agit d'un contrôle utilisateur et il ne sera pas accessible à plusieurs objets pour configurer les étiquettes, donc garder le code comme tel ne l'affectera pas.

158voto

Anthony Pegram Points 58528

Ce que cela signifie, c'est que

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) comme un tableau d'un type de base (dans ce cas, comme un tableau de Controls). Il est également légal, au moment de la compilation, d'assigner un autre 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.

0 votes

Je comprends la différence entre le temps d'exécution et le temps de compilation, comme dans votre exemple, mais la conversion d'un type spécial en type de base n'est-elle pas légale ? De plus, j'ai tapé une liste et je passe de LinkLabel (type spécialisé) à Control (type de base).

2 votes

Oui, la conversion d'un LinkLabel en Control est légale, mais ce n'est pas la même chose que ce qui se passe ici. Il s'agit d'un avertissement concernant la conversion d'un LinkLabel[] a Control[] ce qui est toujours légal, mais peut poser un problème d'exécution. Tout ce qui a changé est la façon dont le tableau est référencé. Le tableau lui-même n'a pas été modifié. Vous voyez le problème ? Le tableau est toujours un tableau du type dérivé. La référence se fait via un tableau du type de base. Par conséquent, il est légal au moment de la compilation d'y affecter un élément du type de base. Pourtant, le type d'exécution ne le supporterait pas.

0 votes

Dans votre cas, je ne pense pas que ce soit un problème, vous utilisez simplement le tableau pour ajouter à une liste de contrôles.

14voto

penartur Points 5450

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

Un type générique est covariant sur un argument de type lorsqu'il renvoie des valeurs de ce type (par ex. Func<out TResult> renvoie des instances de TResult , IEnumerable<out T> renvoie des instances de T ). En d'autres termes, si quelque chose renvoie des instances de TDerived vous pouvez aussi bien travailler avec de telles instances comme si elles étaient de TBase .

Un type générique est contravariant sur un argument de type lorsqu'il accepte des valeurs de ce type (par ex. Action<in TArgument> accepte les instances de TArgument ). En d'autres termes, si quelque chose a besoin d'instances de TBase vous pouvez aussi passer dans les cas de TDerived .

Il semble assez logique que les types génériques qui acceptent et renvoient des instances d'un certain type (sauf s'il est défini deux fois dans la signature du type générique, par ex. CoolList<TIn, TOut> ) ne sont ni covariants ni contravariants sur l'argument de type correspondant. Par exemple, List est défini dans .NET 4 comme List<T> no List<in T> o List<out T> .

Certaines raisons de compatibilité ont pu amener Microsoft à ignorer cet argument et à rendre les tableaux covariants sur leur argument de type de valeurs. Ils ont peut-être mené une analyse et constaté que la plupart des gens n'utilisent les tableaux que comme s'ils étaient en lecture seule (c'est-à-dire qu'ils n'utilisent les initialisateurs de tableaux que pour écrire des données dans un tableau) et, en tant que tels, les avantages l'emportent sur les inconvénients causés par d'éventuelles erreurs d'exécution lorsque quelqu'un essaie d'utiliser la covariance lors de l'écriture dans le tableau. C'est pourquoi elle est autorisée mais pas encouragée.

Quant à votre question initiale, list.ToArray() crée un nouveau LinkLabel[] avec des valeurs copiées à partir de la liste originale, et, pour se débarrasser de l'avertissement (raisonnable), vous devrez passer en Control[] a AddRange . list.ToArray<Control>() fera l'affaire : ToArray<TSource> accepte IEnumerable<TSource> comme argument et renvoie TSource[] ; List<LinkLabel> implémente la lecture seule IEnumerable<out LinkLabel> qui, grâce à IEnumerable covariance, pourrait être passé à la méthode acceptant IEnumerable<Control> comme argument.

11voto

Stuart Golodetz Points 12679

L'avertissement est dû au fait que vous pourriez théoriquement ajouter un fichier Control autre qu'un LinkLabel à la LinkLabel[] à travers le Control[] référence à celle-ci. Cela provoquerait une exception d'exécution.

La conversion a lieu ici parce que AddRange prend un Control[] .

Plus généralement, la conversion d'un conteneur d'un type dérivé en un 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 répondent pas à cette exigence.

11voto

Chris Marisic Points 11495

La "solution" la plus directe

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

Maintenant, puisque vous changez de façon covariante List<LinkLabel> a IEnumerable<Control> il n'y a plus de souci puisqu'il n'est pas possible d'"ajouter" un élément à un énumérable.

5voto

Tim Williams Points 118

La cause première du problème est correctement décrite dans les autres réponses, mais pour résoudre l'avertissement, vous pouvez toujours écrire :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X