我有一个通用方法,定义如下:
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
还有其他建议吗?
根据@Damien_The_Unbeliever的注释回复,C#的类型推断不支持部分推断-必须从调用站点推断all参数(以及返回类型,如果适用),或者必须手动进行指定所有类型参数。
尽管有很多情况下都有解决方法:
如果可以在泛型类中使用静态工厂方法,则可以将方法移至静态类,如果可以推断出父类的类型参数,则可以将其移至该方法:
public class Foo<T>
{
public static Bar CreateBar( T item )
{
// ...
}
}
示例呼叫站点:
Bar bar = Foo<Coffee>.Bar( starbucks );
替代:
public static class Foo
{
public 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
方法包装在另一个方法调用中,其中parent context已经提供了一些类型参数,例如父类的类型参数或作为类的static的类型参数。只能推断参数类型的方法。
所以,您可以将这些通用类型与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
参数参数内进行声明的能力来指定重载,以及如何名为_
的变量/参数将被丢弃:
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...
您真的不能,这就是为什么。...鉴于下面的方法可行,现在,如果您想使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.
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();
}
}