2010-08-20 4 views
16

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 (jette une exception runtime sur le casting)?Génériques et le casting - ne peut pas attribuer la classe héritée à la classe de base

public abstract class EntityBase { } 
public class MyEntity : EntityBase { } 

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

Et maintenant la ligne de coulée:

MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever 
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo; 

Alors, quelqu'un peut-il expliquer comment 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?

+1

Merci à tous pour les réponses. Pour le rendre court - j'ai maintenant résolu ce problème avec une interface de base (RepositoryBase : IRepository). Il s'avère que j'ai juste besoin d'exécuter les fonctions sur l'instance que je reçois et que la classe elle-même gère d'autres choses. – Jefim

+0

Consultez la [FAQ sur la covariance et la contravariance C#] (http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx) –

Répondre

24

RepositoryBase<EntityBase> est et non une classe de base de MyEntityRepository. Vous recherchez variance générique qui existe en C# dans une certaine mesure, mais ne s'applique pas ici.

Supposons que votre classe RepositoryBase<T> avait une méthode comme ceci:

void Add(T entity) { ... } 

Considérons maintenant:

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

Maintenant que vous avez ajouté un autre type d'entité à un MyEntityRepository ... et qui peut ne sois pas juste.

Fondamentalement, la variance générique n'est sûre que dans certaines situations. En particulier covariance générique (ce qui est ce que vous décrivez ici) est seulement sûr lorsque vous obtenez seulement des valeurs "out" de l'API; contravariance (qui fonctionne dans l'autre sens) n'est sûr que lorsque vous ne mettez que des valeurs dans l'API (par exemple une comparaison générale qui peut comparer deux formes par zone peut être considérée comme une comparaison de carrés).

Dans C# 4, il est disponible pour les interfaces génériques et les délégués génériques, et non pour les classes - et uniquement avec les types de référence. Voir MSDN pour plus d'informations, lire <prise> lire C# in Depth, 2nd edition, chapitre 13 </prise > ou blog series Eric Lippert sur le sujet. Aussi, j'ai donné une heure de conversation à ce sujet au NDC en juillet 2010 - la vidéo est disponible here; recherchez simplement "variance".

+0

Merci Jon, tous deux pour l'explication et le lien vers du matériel très intéressant. Je vais certainement envisager de lire le livre et regarder la vidéo. – Jefim

6

Cela nécessite covariance ou contravariance, dont le support est limité dans .Net, et ne peut pas être utilisé sur les classes abstraites. Vous pouvez utiliser la variance sur les interfaces, donc une solution possible à votre problème est de créer un IRepository que vous utilisez à la place de la classe abstraite.

public interface IRepository<out T> where T : EntityBase { //or "in" depending on the items. 
    } 
    public abstract class RepositoryBase<T> : IRepository<T> where T : EntityBase { 
    } 
    public class MyEntityRepository : RepositoryBase<MyEntity> { 
    } 

    ... 

    IRepository<EntityBase> baseRepo = (IRepository<EntityBase>)myEntityRepo; 
11

Chaque fois que quelqu'un pose cette question, j'essaie de prendre leur exemple et à traduire quelque chose en utilisant plus bien connus des classes qui est manifestement illégal (c'est ce Jon Skeet has done in his answer, mais je vais prendre un peu plus loin par effectuer cette traduction).

Remplaçons MyEntityRepository avec MyStringList, comme ceci:

class MyStringList : List<string> { } 

Maintenant, vous semblez vouloir MyEntityRepository d'être castées RepositoryBase<EntityBase>, le raisonnement étant que cela devrait être possible puisque MyEntity dérive de EntityBase.

Mais string dérive de object, n'est-ce pas? Donc, par cette logique, nous devrions être en mesure de lancer un MyStringList à List<object>.

Voyons voir ce qui peut arriver si nous permettons que ...

var strings = new MyStringList(); 
strings.Add("Hello"); 
strings.Add("Goodbye"); 

var objects = (List<object>)strings; 
objects.Add(new Random()); 

foreach (string s in strings) 
{ 
    Console.WriteLine("Length of string: {0}", s.Length); 
} 

Uh-oh. Soudain, nous énumérons un List<string> et nous tombons sur un objet Random. Ce n'est pas bon.

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

+4

J'ai commencé à aller plus loin dans les exemples - j'utilisais des cordes et des objets, mais à la suggestion d'Eric Lippert, j'ai commencé à utiliser des objets du monde réel ... Fruit/Apple/Banana fonctionne bien en termes de "you can" t ajouter une pomme à un tas de bananes ". –

Questions connexes