Génériques et conversion – impossible de convertir la classe héritée en classe de base

Je sais que c’est vieux, mais je ne suis toujours pas très bon pour comprendre ces problèmes. Quelqu’un peut-il me dire pourquoi ce qui suit ne fonctionne pas (lève une exception d’ runtime propos du casting)?

 public abstract class EntityBase { } public class MyEntity : EntityBase { } public abstract class RepositoryBase where T : EntityBase { } public class MyEntityRepository : RepositoryBase { } 

Et maintenant la ligne de casting:

 MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever RepositoryBase baseRepo = (RepositoryBase)myEntityRepo; 

Alors, quelqu’un peut-il expliquer en quoi cela est invalide? Et, vous n’êtes pas d’humeur à expliquer – y a-t-il une ligne de code que je peux utiliser pour faire ce casting?

RepositoryBase n’est pas une classe de base de MyEntityRepository . Vous recherchez une variance générique qui existe en C # dans une mesure limitée, mais ne s’appliquerait pas ici.

Supposons que votre classe RepositoryBase ait une méthode comme celle-ci:

 void Add(T entity) { ... } 

Considérons maintenant:

 MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever RepositoryBase baseRepo = (RepositoryBase)myEntityRepo; baseRepo.Add(new OtherEntity(...)); 

Maintenant, vous avez ajouté un type d’entité différent à un MyEntityRepository … et cela ne peut pas être correct.

Fondamentalement, la variance générique n’est sans danger que dans certaines situations. En particulier, la covariance générique (que vous décrivez ici) n’est sûre que lorsque vous obtenez uniquement des valeurs “sortantes” de l’API; La contravariance générique (qui fonctionne à l’inverse) n’est sûre que si vous ne placez jamais de valeurs “dans” l’API (par exemple, une comparaison générale permettant de comparer deux formes par surface peut être considérée comme une comparaison de carrés).

En C # 4, cela est disponible pour les interfaces génériques et les delegates génériques, pas pour les classes – et uniquement avec les types de référence. Voir MSDN pour plus d’informations. Pour plus d’informations, consultez , lisez C # in Depth, 2e édition , chapitre 13 ou la série de blogs d’ Eric Lippert sur le sujet. De plus, j’ai donné une conférence à ce sujet pendant une heure au NDC en juillet 2010 – la vidéo est disponible ici .

Chaque fois que quelqu’un pose cette question, j’essaie de prendre son exemple et de le traduire en quelque chose utilisant des classes plus connues qui sont évidemment illégales (c’est ce que Jon Skeet a fait dans sa réponse ; mais je vais encore plus loin en effectuant cette traduction).

MyEntityRepository par MySsortingngList , comme ceci:

 class MySsortingngList : List { } 

Maintenant, vous semblez vouloir que le MyEntityRepository puisse être converti en RepositoryBase , le raisonnement étant que cela devrait être possible puisque MyEntity dérive de EntityBase .

Mais la ssortingng dérive d’un object , n’est-ce pas? Donc, par cette logique, nous devrions pouvoir MySsortingngList une List MySsortingngList en une List .

Voyons ce qui peut arriver si nous permettons cela …

 var ssortingngs = new MySsortingngList(); ssortingngs.Add("Hello"); ssortingngs.Add("Goodbye"); var objects = (List)ssortingngs; objects.Add(new Random()); foreach (ssortingng s in ssortingngs) { Console.WriteLine("Length of ssortingng: {0}", s.Length); } 

Uh-oh. Soudain, nous énumérons une List et nous tombons sur un object Random . Ce n’est pas bon.

Espérons que cela rend le problème un peu plus facile à comprendre.

Cela nécessite une covariance ou une contravariance, dont le support est limité en .Net, et ne peut pas être utilisé sur des classes abstraites. Vous pouvez cependant utiliser variance sur les interfaces. Par conséquent, une solution possible à votre problème consiste à créer un référentiel IR que vous utiliserez à la place de la classe abstraite.

  public interface IRepository where T : EntityBase { //or "in" depending on the items. } public abstract class RepositoryBase : IRepository where T : EntityBase { } public class MyEntityRepository : RepositoryBase { } ... IRepository baseRepo = (IRepository)myEntityRepo;