1

When I tried to answer the question:

Is it possible to get rid of the TClient generic type in the Service class

I found a strange usage that I've never designed something of this kind of uncompilable syntax, and following is a represent of what I encountered:

interface IGeneric<T> {
}

partial class SomeClass {
    // won't compile
    public static void SomeMethod<U>(Action<T> d) where T: IGeneric<U> {
    }
}

And even if declared as:

class Concrete: IGeneric<object> {
}

partial class SomeClass {
    public static void SomeMethod<U>(Action<IGeneric<U>> d) { // compiles
    }
}

would not make the following code compile-able:

var d=default(Action<Concrete>);
SomeClass.SomeMethod(d); // won't compile

I'm not aware a syntax that works without both type parameters involved.

So I'm wondering is there a syntax does this kind of a reversed type inference? Or a workaround?

Community
  • 1
  • 1
Ken Kin
  • 4,503
  • 3
  • 38
  • 76

2 Answers2

4

The simple answer is no. This isn't even initially about type inference - it's about type constraints. You can only add constrain a type parameter which is introduced in the same declaration. So this:

public static void SomeMethod<U>(Action<T> d) where T: IGeneric<U>

is invalid because you're trying to constrain T in terms of U, when it's U which was actually introduced in the method declaration. Indeed, T itself isn't a type parameter anywhere - but this would fail even if SomeClass were generic in T.

In many situations similar to this you can go via an extra static method in a non-generic type, to create an instance of a generic type via type inference - but the specifics are usually that you've got two type parameters and you want to specify one of them explicitly.

One important point to note is that an Action<Concrete> simply is not an Action<IGeneric<object>>. For example, Concrete may expose some extra property which an Action<Concrete> could depend on - but given an Action<IGeneric<object>> you could easily call that with a different implementation of IGeneric<object>. Your existing SomeMethod tries to sort of work around that by specific Action<U> instead of Action<IGeneric<T>> - but at that point it's relatively hard to use the action. This is rarely (in my experience) a practical approach, even when type inference works.

As soon as you change to a genuinely covariant delegate (and assuming you're using C# 4), then unless you care about U you can simply use a different signature:

using System;

interface IGeneric<T> {}

class SomeClass
{    
    public static void SomeMethod<T>(Func<IGeneric<T>> d) {}
}

class Concrete: IGeneric<object> {}

class Test
{
    static void Main()
    {
        var d = default(Func<Concrete>);
        // This compiles fine
        SomeClass.SomeMethod(d);
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you! So there really isn't any syntax does that .. The `type inference` I mentioned, what I mean is *use `T` to infer `U` with a given constraint*, is that wrong using of terminology? – Ken Kin Apr 26 '13 at 06:37
  • @KenKin: Sort of. Type inference is the act of the compiler inferring type arguments from regular arguments. You can constrain `T` in terms of `U`, but only if *both* of them are declared as type parameters somewhere - and unfortunately C#'s type inference isn't actually smart enough to infer both `T` and `U` from a single argument in this case. – Jon Skeet Apr 26 '13 at 06:43
  • Not smart enough?? Wondering .. I guess it would cause error-prone of coding if the compiler can do that, as the answer `is rarely a ..`; however, are there really compilers(of what language) CAN do? – Ken Kin Apr 26 '13 at 07:04
  • @KenKin: There are plenty of languages with fuller type inference than C#, both for this sort of thing and implicit typing elsewhere. There are lots of factors to balance when designing a language, and different languages give different weights to those factors. – Jon Skeet Apr 26 '13 at 07:07
  • Thank you and I think the answer is quite clear and detailed. – Ken Kin Apr 29 '13 at 10:40
2

The problem is that you are trying to treat an Action<T> as being covariant in T but it isn't. In fact, it's contravariant.

For example, if you had a covariant delegate, like this.

delegate T CovariantCall<out T>();

You could easily do what you're asking.

CovariantCall<IGeneric<object>> covariant = default(CovariantCall<Concrete>);

Your first example doesn't compile because you've omitted the T in the declared type parameters list for the method. It's a better idea, though, and it works because the constraint only verifies and doesn't affect parameter variance, but you would then have to explicitly specify which parameters you're looking for and can't infer it.

public static void SomeMethod<T, U>(Action<T> d) where T: IGeneric<U>
{ 
    ...
}

SomeClass.SomeMethod<Concrete, object>(default(Action<Concrete>));

There are limits to what C# type inference is capable of, and this is one of them. You can't do what you're asking without explicitly specifying the types.

Chris Hannon
  • 4,134
  • 1
  • 21
  • 26
  • I've upvoted, however, Mr. Skeet's answer elaborate in more detail, so I marked. Thank you very much, especially for be the first to answer. – Ken Kin Apr 29 '13 at 10:47