Covariance Contravariance Generics Interfaces - why doesn't this work?

Thank you in advance. Here is my code:

public class ApplicationUser : IdentityUser
{
    public ApplicationUser()
    {

    }
}

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

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

Why is this not working?

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

Since ApplicationUser inherits from IdentityUser and AppUserManager from UserManager, why is the combined generic not accepted? Thanks!

Upvotes: 0

Views: 176

Answers (2)

Eric Lippert
Eric Lippert

Reputation: 660032

Matias's answer is good; I thought I'd add a bit more context. Let's again simplify your example:

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

The question now is "why is this conversion illegal?"

Cage<Animal> cage = new SpecialTigerCage();

It should be obvious now why this is illegal. You can put a giraffe into a cage that can contain animals, but if you put a giraffe into a special tiger cage, the giraffe is not going to be very happy about it.

The type system cannot prove to its satisfaction that you're not going to put a giraffe into that tiger cage, so it disallows the conversion.

As others pointed out, C# does support this sort of covariant conversion on interfaces and delegates, where it can prove that you're not going to put a giraffe into a tiger cage. IEnumerable<T> for example is covariant. A sequence of tigers may be used where a sequence of animals is needed because IEnumerable<T> provably has no method that can insert a giraffe into the sequence.

Upvotes: 3

Mat&#237;as Fidemraizer
Mat&#237;as Fidemraizer

Reputation: 64923

Both contravariance and covariance on generic type parameters for classes isn't supported.

Simplifying your issue:

// Compiler error!
UserManager<IdentityUser> userManager = new AppUserManager();
  • AppUserManager inherits UserManager<ApplicationUser>.
  • Thus, you're trying to set an UserManager<ApplicationUser>-derived reference on a UserManager<IdentityUser> reference. This is the problem! They're different types.

OP said...:

which, essentially means, I can't use concrete classes and their generics in an interface and expect them to be implemented by their children?

Interfaces support variance. Thus, you can design your interface as follows:

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

...and once you've implemented this interface, your implementation will declare a property of the concrete TManager type.

Upvotes: 4

Related Questions