2015-12-16 2 views
0

Merci d'avance. Voici mon code:Covariance Contravariance Generics Interfaces - pourquoi cela ne fonctionne-t-il pas?

public class ApplicationUser : IdentityUser 
{ 
    public ApplicationUser() 
    { 

    } 
} 

public class AppUserManager : UserManager<ApplicationUser> 
{ 
... 
} 

public interface IOwinManager 
{ 
    UserManager<IdentityUser> UserManager { get; } 
} 

Pourquoi cela ne fonctionne-t-il pas?

public class OwinManager : IOwinManager 
{   
    public UserManager<IdentityUser> UserManager 
    { 
     get { return new AppUserManager(); } 
    } 
} 

Depuis ApplicationUser hérite de IdentityUser et AppUserManager de UserManager, pourquoi le non générique combinée acceptée? Merci!

+0

Est-ce que 'UserManager <>' 'est une classe, pas une interface?Tout simplement parce que "un' ApplicationUser' *** est un *** 'IdentityUser'" n'implique pas que "un' UserManager '*** est un ***' UserManager '". Sauf si le type 'UserManager <>' a été déclaré _covariant_ dans son argument de type. En C# dans sa version actuelle, seuls les types 'interface' et' delegate' peuvent être déclarés covariants (modificateur 'out' sur le paramètre type). Les types 'class' et' struct' ne le peuvent pas. –

Répondre

4

La contravariance et la covariance sur les paramètres de type générique pour les classes ne sont pas prises en charge.

votre question Simplifier:

// Compiler error! 
UserManager<IdentityUser> userManager = new AppUserManager(); 
  • AppUserManager hérite UserManager<ApplicationUser>. Par conséquent, vous essayez de définir une référence UserManager<ApplicationUser> -derived sur une référence UserManager<IdentityUser>. C'est le problème! Ils sont différents types.

OP a dit ...:

qui, signifie essentiellement, je ne peux pas utiliser des classes concrètes et leurs génériques dans une interface et attendre qu'ils soient mis en œuvre par leurs enfants?

Les interfaces prennent en charge la variance. Ainsi, vous pouvez concevoir votre interface comme suit:

public interface IOwinManager<out TUser, out TManager> 
    where TUser : IdentityUser 
    where TManager : UserManager<TUser> 
{ 
    TManager UserManager { get; } 
} 

... et une fois que vous avez mis cette interface, votre implémentation déclarera une propriété du type TManager béton.

+0

ce qui, essentiellement signifie, je ne peux pas utiliser des classes concrètes et leurs génériques dans une interface et s'attendre à ce qu'ils soient mis en œuvre par leurs enfants? –

+0

@EvangelosAktoudianakis Vérifier ma mise à jour –

3

La réponse de Matias est bonne; Je pensais que j'ajouterais un peu plus de contexte. Encore une fois simplifier votre exemple:

class Animal {} // IdentityUser 
class Tiger : Animal {} // ApplicationUser  
class Giraffe : Animal {} // some other kind of user 
class Cage<T> where T : Animal {} // UserManager 
class SpecialTigerCage : Cage<Tiger> {} // AppUserManager 

La question est maintenant "pourquoi cette conversion est-elle illégale?"

Cage<Animal> cage = new SpecialTigerCage(); 

Il devrait être évident maintenant pourquoi c'est illégal. Vous pouvez mettre une girafe dans une cage qui peut contenir des animaux, mais si vous mettez une girafe dans une cage de tigre spéciale, la girafe ne va pas être très heureux à ce sujet.

Le système de type ne peut pas prouver à sa satisfaction que vous n'allez pas mettre une girafe dans cette cage de tigre, donc il interdit la conversion. Comme d'autres l'ont fait remarquer, C# supporte ce type de conversion covariante sur les interfaces et les délégués, où peut prouver que vous n'allez pas mettre une girafe dans une cage de tigre. Par exemple, IEnumerable<T> est covariant. Une séquence de tigres peut être utilisée lorsqu'une séquence d'animaux est nécessaire parce que IEnumerable<T> ne possède pas de méthode qui puisse insérer une girafe dans la séquence.

+0

Merci - cela le rend un peu plus clair, mais je suis toujours confus. Peut-être que je m'embrouille, parce que je m'attends à cela - devrais-je alors ajouter une girafe dans une cage tigercage mise en œuvre, alors si je l'ai explicitement envoyé où T: Animal, je m'attendrais seulement à appeler des méthodes animales. Donc, je suppose que -est possible, pourvu que je déclare explicitement T comme étant de type animal. Comme vous l'avez fait? Cela devrait être légal, si tout se passe comme Animal? –

+0

@EvangelosAktoudianakis: Tout jeter à Animal fonctionne bien quand on tire des éléments (je voulais un Animal et on lui a donné un Tigre) mais pas quand on mettait des éléments (la collection voulait un Tigre et je lui donnais un Animal). Il est assez facile de donner à votre Cage deux interfaces, une interface pour mettre les animaux et une interface pour extraire les choses (par exemple, IEnumerable , mais vous pouvez définir les vôtres). Si vous souhaitez que cette diffusion soit gérée par le système de types, utilisez des interfaces dans vos signatures de fonction. – Brian

+0

@EvangelosAktoudianakis: La question n'est pas quelles méthodes seront appelées sur les objets de l'heure de compilation de type T. Vous avez raison de dire que les méthodes d'Animal seraient appelées, mais ce n'est pas pertinent. Le problème est, supposons 'Cage où T: Animal' a une méthode' AddAnimal (T) '. Donc 'Cage ' a une méthode 'AddAnimal (Animal)'. Mais SpecialTigerCage n'implémente que AddAnimal (Tiger); il ne peut pas remplir toutes les fonctions requises d'un «Cage », il ne peut donc pas être utilisé comme tel. –