仅推断具有多个泛型类型的一种类型

Inferring only one type with multiple generic types

我有一个通用方法,定义如下:

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

我希望可以这样称呼它:

int result = Test<int>(5.0);

而不是像这样:

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

显然,语言语法不允许这样的事情。我正在寻找一种简单的方法来做到这一点,因为 B 类型,在我的例子中,通常会很长,我想避免基本上只是方法调用的长代码行。

类似这样的东西是可行的,但是很丑陋:

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

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

还有其他建议吗?

你真的不能,这就是为什么......鉴于下面的工作,现在如果你想推断 b....

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

如果您将 **string b** 更改为类型推断,您将取回您拥有的内容。 就好像你想省略类型那么编译器如何知道类型。

你可以做(​​不建议只是说)

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

但是你又不知道它的类型,所以你需要检查 将其拆箱为其类型。

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

喜欢

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

根据@Damien_The_Unbeliever的评论回复,C#的类型推断不支持部分推断——所有参数(和return -type,如果适用)必须从调用站点推断 - 或者您必须手动指定所有类型参数。

虽然很多情况下都有解决方法:

具有可能推断类型参数的静态方法:

如果您在泛型 class 中有一个静态工厂方法,您可以将该方法移至静态 class 并将父 class' 类型参数移至该方法,如果可以推断:

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

示例调用站点:

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

选择:

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

示例调用站点:

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

具有不可推断类型参数的方法:

具有无法从调用站点推断的类型参数的方法可以转换为具有部分参数应用程序的新泛型方法,如下所示:

考虑:

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

示例调用站点:

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

但是,我们可以将这个 DoSomething 方法包装在另一个方法调用中,其中一些类型参数已经由 父上下文 提供,例如父 class 的类型参数或作为 class 静态方法的类型参数,仅具有可推断的参数类型。

因此,您 可以 将这些通用类型部分地应用到 Func<>,就像这样:

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

示例调用站点:

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

未使用的参数:

另一个技巧是通过使用未使用的 out 参数创建重载来利用类型推断最适合参数的方式,这些参数可以使用 C# 7.0 在 out 参数内进行声明的能力来指定调用站点中的参数以及如何丢弃名为 _ 的 variables/parameters:

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

示例调用站点:

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

像这样:

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

示例调用站点:

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

合并:

这可以与带有 out 参数的新委托定义结合用于不可推断的类型参数(因为我们不能使用 Func<> 因为它没有列出任何 out参数):

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