2

I have a generic method, defined like this:

public static A Test<A, B>(B b)
    where A : new()
{
    return new A();
}

I would like to be able to call it something like this:

int result = Test<int>(5.0);

Instead of like this:

int result = Test<int, double>(5.0);

Clearly, the language syntax does not allow for something like that. I'm looking for a simple way to do this, since the B type, in my case, will generally be something long and I would like to avoid long lines of code that are basically just method calls.

Something like this is doable, but ugly:

A Test<A, B>(B b, A placeholder) where A : new() => new A(); // Definition

int result = Test(5.0, 2); // Method call

Are there any other suggestions for doing this?

Dai
  • 141,631
  • 28
  • 261
  • 374
Igor Ševo
  • 5,459
  • 3
  • 35
  • 80
  • 3
    Type inference is an all-or-nothing system. You either have it infer everything or you have to tell it everything. – Damien_The_Unbeliever Mar 12 '20 at 10:35
  • I found this [answer by Eric Lippert](https://stackoverflow.com/a/8511493/5174469). Even though it is an old answer, it might still give valueable insight. – Mong Zhu Mar 12 '20 at 10:38
  • see answer but if you simply want to convert double to ints i would make an extension method rather than do this buy generics `double.ToInt()` <- extension method – Seabizkit Mar 12 '20 at 11:09

2 Answers2

3

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... 
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Dai
  • 141,631
  • 28
  • 261
  • 374
0

You cant really and here is why....given the below which works, now if you wanted to make b inferred....

public class MyFancyClass
{

}
public static class Test
{
    public static A Method<A>(**string b**) where A : new()
    {
        return new A();
    }
}
static async Task Main(string[] args)
{
   var result = Test.Method<MyFancyClass>("5.0");
}

if you changed **string b** to be type inferred you would get back what you had. as if you want to omit the type then how would the compiler know the type.

you could do (not suggesting just saying)

public static A Method<A>(**object**) where A : new()
{
    return new A();
}

but again you wouldn't know its type, so you would then need to check to unbox it to its type.

if( b is string)
{
}
else if(b is int)
{
}
else if (b is double)
{
}
.... and continue.

like

public static class Test
{
    public static A Method<A>(object b) where A : new()
    {
        if (b is string)
        {
        }
        else if (b is int)
        {
        }
        else if (b is double)
        {
        }

        return new A();
    }
}
Seabizkit
  • 2,417
  • 2
  • 15
  • 32