Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic type specification arguments - sometimes optional, but not always

Tags:

c#

generics

This question is about when you do and don't need to include generic type specification arguments. It's a bit lengthy, so please bear with it.

If you have the following (contrived) classes...

public abstract class UserBase
{
    public void DoSomethingWithUser() { }
}

public class FirstTimeUser : UserBase
{
     /* TODO: some implementation */
}

The following method...

private static void DoThingsWithUser<TUser>(TUser user) where TUser : UserBase
{
    user.DoSomethingWithUser();
}

Can be called with or without having to specify the type argument TUser...

var user = new FirstTimeUser();

DoThingsWithUser<FirstTimeUser>(user);
DoThingsWithUser(user); // also valid, and less typing required!

So far, so good.

But if you add a couple more (again, contrived) classes...

public abstract class UserDisplayBase<T> where T : UserBase
{
    public T User { get; protected set; }
}

public class FirstTimeUserDisplay : UserDisplayBase<FirstTimeUser>
{
    public FirstTimeUserDisplay()
    {
        User = new FirstTimeUser();
    }
}

And a method...

private static void DoThingsWithUserDisplay<TDisplay, TUser>(TDisplay userDisplay)
    where TDisplay : UserDisplayBase<TUser>
    where TUser : UserBase
{
    userDisplay.User.DoSomethingWithUser();
}

When calling this method, it is mandatory to include the type arguments...

var userDisplay = new FirstTimeUserDisplay();
DoThingsWithUserDisplay<FirstTimeUserDisplay, FirstTimeUser>(userDisplay); // Type arguments required!

If you don't specify the type arguments, you will get a compiler error of

The type arguments for method 'DoThingsWithUserDisplay(TDisplay)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

I think that the compiler should/could be smart enough to figure this out...or is there a subtle reason why not?

like image 803
Richard Ev Avatar asked Feb 13 '15 14:02

Richard Ev


1 Answers

As the compiler is analyzing the types to infer, it reaches a dead end when looking at DoThingsWithUserDisplay(userDisplay);

It finds that TDisplay must be of type FirstTimeUserDisplay, which is good. Then the constraint says that TDisplay must inherit from UserDisplayBase<TUser>.

Since it does not know the type for TUser it cannot determine if FirstTimeUserDisplay inherits from UserDisplayBase<TUser> and it also can't infer that the TUser parameter should whatever type is in the generic UserDisplayBase<TUser>.

Edit: Incidentally, you can get the type inference you seek using an interface with a variant. In this case, the interface definition provides sufficient inheritance information so that the constraints are met.

public abstract class UserDisplayBase<T> : IUserDisplayBase<T>
    where T : UserBase
{
    public T User { get; protected set; }
}

public interface IUserDisplayBase<out T>
    where T : UserBase
{
    T User { get; }
}

private static void DoThingsWithUserDisplay<TDisplay>(TDisplay userDisplay)
    where TDisplay : IUserDisplayBase<UserBase>
{
    userDisplay.User.DoSomethingWithUser();
}

can be called with

var userDisplay = new FirstTimeUserDisplay();
DoThingsWithUserDisplay(userDisplay);
like image 145
Grax32 Avatar answered Oct 30 '22 22:10

Grax32