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...