6

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?

Richard Ev
  • 52,939
  • 59
  • 191
  • 278
  • My guess is that the compiler couldn't do this for an interface using co/contravariance, and they didn't think it was worthwhile special-casing that and doing it in other situations. – Ben Aaronson Feb 13 '15 at 14:11
  • 1
    This *might* be relevant: http://stackoverflow.com/questions/8511066/why-doesnt-c-sharp-infer-my-generic-types – Matthew Watson Feb 13 '15 at 14:13
  • 1
    @MatthewWatson - definitely a related question, but I'm wondering _why_ the compiler doesn't/can't infer the type arguments. Which I think is slightly different enough to prevent my question getting closed. :) – Richard Ev Feb 13 '15 at 14:20
  • 1
    Yes, I didn't think it was a duplicate - just related enough to be of interest. – Matthew Watson Feb 13 '15 at 14:25
  • 1
    I'm willing to bet quite a bit of virtual money that [Eric Lippert's writeups on type inference](http://ericlippert.com/category/csharp/type-inference/) will somehow completely explain this after you've read all of them. Of course, I myself am too lazy to do this right now and construct the perfect answer from them... but who knows, maybe the master himself will grace us with his presence. :-) – Jeroen Mostert Feb 13 '15 at 14:26
  • 1
    Yes, this blog too: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – Matthew Watson Feb 13 '15 at 14:26

1 Answers1

2

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);
Grax32
  • 3,986
  • 1
  • 17
  • 32