C# Generic Type Inference with Multiple Types

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

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);

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.

Type inference with multiple generic type parameters

The reason is that the second type parameter it's not used in any of the function parameters. So there is no way it's type can be inferred purely from parameter usage.

If you were to have a method signature like this (obviously not equivalent to your code, just an example):

public static TResult DoStuffWithThings<TContainer, TResult>(
this TContainer container,
Func<TContainer, TResult> getSomething)

Then it would be able to infer the generic types from the parameters.

If you want to avoid specifying the first parameter you can split the method up into two functions and hide intermediate parameters in a generic type, like this:

public static IntermediateResult<T> DoStuffWithThings<T>(this T container)

public class IntermediateResult<T>
{
public WithReturnType<TResult>()
}

Then it calling as

var result = container.DoStuffWithThings().WithReturnType<Result>();

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.

Call generic Method with multiple generic Types without specifying every single generic Type

In C#, there is no partial type inference: You must either specify all or none of the generic arguments, but the latter only works if the compiler is able to infer all types from context. Return types cannot be infered (except for some special cases such as lambdas), so what you want to do is not possible.

Two workarounds:

A: Move T1 from the method to the class

class Example<T1>
{
T1 GenericMethod<T2, T3>(T2 parameter1, T3 parameter2)

B: Replace T2 and T3 by object

class Example
{
T1 GenericMethod<T1>(object parameter1, object parameter2)

Generic extension method type inference

You can get most of what you are looking for by using a fluent interface:

void Main()
{
var b = new ushort[] { 1, 256 };

// We now use a fluent interface to do the conversion - T1 is
// now inferred.
var us = b.Convert().To<byte>();

us.Dump();
}

public static class Extensions
{
public static IConverter Convert<T1>(this T1[] buffer)
{
return new Converter<T1>(buffer);
}
}

public interface IConverter
{
T2[] To<T2>();
}

public class Converter<T1> : IConverter
{
private readonly T1[] _buffer;
public Converter(T1[] buffer)
{
_buffer = buffer ?? throw new ArgumentNullException();
}

public T2[] To<T2>()
{
var bufferNumBytes = _buffer.Length * Marshal.SizeOf(default(T1));
var targetElementNumBytes = Marshal.SizeOf(default(T2));
if (bufferNumBytes % targetElementNumBytes != 0)
{
throw new ArgumentException($"Array must have multiple of {targetElementNumBytes} bytes, has {bufferNumBytes} bytes instead", nameof(_buffer));
}
var res = new T2[bufferNumBytes / targetElementNumBytes];
Buffer.BlockCopy(_buffer, 0, res, 0, bufferNumBytes);
return res;
}
}

The downsides are you are allocating extra memory for the Converter class, and you end up calling two methods - but discoverability should be basically as good as your original example, which is what I think is really motivating your question.

How does type inference work with overloaded generic methods

Assuming your Add methods are in the class named DataCollector;
you can add extension method which accepts InfoData<ExtraInfo> and returns the same type as shown below.

public static class DataCollectorExtensions
{
public static InfoData<ExtraInfo> AddInfoData(this DataCollector dataCollector, InfoData<ExtraInfo> data)
{
return dataCollector.Add<InfoData<ExtraInfo>, ExtraInfo>(data);
}
}

In this way you have to specify the generic parameters only once inside the extension method and use the extension method elsewhere without need to specify generic parameter.

new DataCollector().AddInfoData(new InfoData<ExtraInfo>());

You still have to name the method other than Add (I have named AddInfoData) otherwise compiler again picks up the public TData Add<TData>(TData data) method in the the DataCollector class.
As you're infact adding InfoData this method name should be acceptable.
I suppose this solution should be acceptable for all practical purposes.



Related Topics



Leave a reply



Submit