Partial Generic Type Inference Possible in C#

Partial generic type inference possible in C#?

If you have only two specific types of registration (which seems to be the case in your question), you could simply implement two extension methods:

public static DelegateRegistration Parameter<T>( 
this DelegateRegistration p, string name, T value);

public static ConcreteTypeRegistration Parameter<T>(
this ConcreteTypeRegistration p, string name, T value);

Then you wouldn't need to specify the type argument, so the type inference would work in the example you mentioned. Note that you can implement both of the extension methods just by delegation to a single generic extension method with two type parameters (the one in your question).


In general, C# doesn't support anything like o.Foo<int, ?>(..) to infer only the second type parameter (it would be nice feature - F# has it and it's quite useful :-)). You could probably implement a workaround that would allow you to write this (basically, by separating the call into two method calls, to get two places where the type inferrence can be applied):

FooTrick<int>().Apply(); // where Apply is a generic method

Here is a pseudo-code to demonstrate the structure:

// in the original object
FooImmediateWrapper<T> FooTrick<T>() {
return new FooImmediateWrapper<T> { InvokeOn = this; }
}
// in the FooImmediateWrapper<T> class
(...) Apply<R>(arguments) {
this.InvokeOn.Foo<T, R>(arguments);
}

Is is possible to make a generic method with two type arguments only infer one of them?

Yes, you always need to specify both types. Type inference only works when you specify all the type arguments.

Yes, the C# compiler cannot infer types based on constraints. It can only infer the types based on the types you pass in as arguments.

On a side note, there is an open issue about improving type inference in this regard, however it doesn't seem to have high priority.

Working around lack of partial generic type inference with constraints

That can't work the usual way around because you can't convince the compiler of the TId constraint after the fact. You can, however, reverse the sequence, i.e.

var obj = ById(id).Find<SomeType>();

Not as elegant, but it works. Implementation:

public Finder<TId> ById<TId>(TId id) where TId : IEquatable<TId>
{
return new Finder<TId>(this, id);
}
public struct Finder<TId> where TId : IEquatable<TId>
{
private readonly YourParent parent;
private readonly TId id;
internal Finder(YourParent parent, TId id)
{
this.id = id;
this.parent = parent;
}
public T Find<T>() where T : class, IEntity<TId>
{
return parent.FindById<T, TId>(id);
}
}

caveat: it is probably easier just to tell it both the parameter types explicitly.

Inferring only one type with multiple generic types

As per @Damien_The_Unbeliever's comment-reply, C#'s type-inference does not support partial inference - either all parameters (and the return-type, if applicable) must be inferred from the call-site - or you must manually specify all type-parameters.

There are workarounds for many cases though:

Static methods with possibly-inferred type arguments:

If you have a static factory method in a generic class you can move the method to a static class and move the parent class' type-argument to the method if it can be inferred:

public class Foo<T>
{
public static Bar CreateBar( T item )
{
// ...
}
}

Example call-site:

Bar bar = Foo<Coffee>.Bar( starbucks ); 

Alternative:

public static class Foo
{
public static Bar CreateBar<T>( T item )
{
// ...
}
}

Example call-site:

Bar bar = Foo.Bar( starbucks );  // voila, type-inference!

Methods with non-inferable type arguments:

Methods that have type arguments that cannot be inferred from the call-site could be converted to new generic methods that have partial parameter application, like so:

Consider:

class Foo<TClass>
{
public TReturn DoSomething<TParam,TUnused,TReturn>( TParam p )
{
// ...
}
}

Example call-site:

Violin stradivarius = ...
Woodwind flute = new Foo<Orchestra>().DoSomething<Violin,Percussion,Woodwind>( stradivarius ); // `Violin` was required and couldn't be inferred.

However, we can wrap this DoSomething method in another method-call where some type-arguments are already supplied by a parent context, such as the parent class's type arguments or as type arguments to a class' static methods with only types for parameters that can be inferred.

So, you can sort-of partially-apply these generic types with Func<>, like so:

class PAReturn<TReturn>
{
public static TReturn Invoke( Func<TReturn> func ) => func();

public static TReturn Invoke<T0>( Func<T0,TReturn> func, T0 arg ) => func( arg );

public static TReturn Invoke<T0,T1>( Func<T0,T1,TReturn> func, T0 arg, T1 arg1 ) => func( arg, arg1 );

public static TReturn Invoke<T0,T1,T2>( Func<T0,T1,T2,TReturn> func, T0 arg, T1 arg1, T2 arg2 ) => func( arg, arg1, arg2 );

// etc
}

class PAReturn<TReturn,T0>
{
public static TReturn Invoke( Func<T0,TReturn> func, T0 arg ) => func( arg );

public static TReturn Invoke<T1>(Func<T0, T1, TReturn> func, T0 arg, T1 arg1) => func(arg, arg1);

public static TReturn Invoke<T1,T2>(Func<T0, T1, T2, TReturn> func, T0 arg, T1 arg1, T2 arg2) => func( arg, arg1, arg2 );
}

Example call-site:

Violin stradivarius = ...
Woodwind flute = PartialAply<Percussion,Woodwind>( new Foo<Orchestra>().DoSomething )( stradivarius ); // Observe that `Violin` was inferred.

Unused parameters:

Another trick is to take advantage of how type inference works best for parameters by creating overloads with unused out parameters which can be specified using C# 7.0's ability to make declarations inside out parameter arguments in call-sites and how variables/parameters named _ are discarded:

class Foo<A>
{
// Original method:
public B GetSomething<B,C,D>( C paramC )
{
// ...
}
}

Example call-site:

Cat bagheera = ...
Mouse m = new Foo<Flea>().GetSomething<Mouse,Cat,Dog>( bagheera ); // `Cat` was not inferred.

Like so:

partial class Foo<A>
{
// Inference helper overload:
public B GetSomething<B,C,D>( out B b, out D d, C c)
{
return this.GetSomething<B,C,D>( c );
}
}

Example call-site:

Cat bagheera = ...
Mouse m = new Foo<Flea>().GetSomething( out Mouse _, out Dog _, bagheera ); // `Cat` was inferred.

Combined:

This can be combined with new delegate definitions with out parameters for non-inferable type parameters (because we can't use Func<> because it doesn't list any out parameters):

delegate TReturn PAFunc<TReturn>( out Return _ );
delegate TReturn PAFunc<T0,TReturn>( out Return _, T0 arg0 );

delegate TReturn PAFunc<T0,T1,TReturn>( out Return _, T0 arg0, T1 arg1 );

delegate TReturn PAFunc<T0,T1,N0,TReturn>( out Return _, out N0 _, T0 arg0 ); // `N0` refers to which one is non-inferrable
// etc...

Partial type inference

You can split t into a generic method on a generic type:

class Foo<TOuter> {
public static void Bar<TInner>(TInner arg) {...}
}
...
int x = 1;
Foo<string>.Bar(x);

Here the int is inferred but the string is explicit.

Infer generic type with two generic type parameters

Because the C# rules don't allow that.

It would be feasible for C# to have a rule where if some of the types related to parameters (and hence could be inferred at least some of the time) and the number of explicitly given types was the same as the number of remaining un-inferrable types, then the two would work in tandem.

This would need someone to propose it, convince other people involved in the decision-making around C# it was a good idea, and then for it to be implemented. This has not happened.

Aside from the fact that features start off having to prove themselves worth the extra complexity they bring (add anything to a language and it is immediately more complex with more work, more chance of compiler bugs etc.) the question then is, is it a good idea?

On the plus, your code in this particular example would be better.

On the negative, everyone's code is now slightly more complicated, because there's more ways something that is wrong can be wrong, resulting either in code that fails at runtime rather than compile-time or less useful error messages.

That people already find some cases around inference to be confusing would point to the idea that adding another complicating case would not be helpful.

Which isn't to say that it is definitely a bad idea, just that there are pros and cons that make it a matter of opinion, rather than an obvious lack.

C# Generic Type Inference With Multiple Types


My question is, why can't TInput be inferred in this situation?

It can - it's TResult which can't be inferred, and there's no way of specifying "partial" inference.

What you can sometimes do is separate the type parameters into ones for a generic type and ones for a generic method, so you'd end up with:

// Explicitly state TResult, and infer TInput
Serializer<MySuperType>.Serialize(x);


Related Topics



Leave a reply



Submit