当我试图回答这个问题时:
Is it possible to get rid of the TClient generic type in the Service class
我发现了一个奇怪的用法,我从来没有设计过这种无法编译的语法,以下是我遇到的代表:
interface IGeneric<T> {
}
partial class SomeClass {
// won't compile
public static void SomeMethod<U>(Action<T> d) where T: IGeneric<U> {
}
}
即使宣布为:
class Concrete: IGeneric<object> {
}
partial class SomeClass {
public static void SomeMethod<U>(Action<IGeneric<U>> d) { // compiles
}
}
将不使以下代码可编译:
var d=default(Action<Concrete>);
SomeClass.SomeMethod(d); // won't compile
我不知道一个语法是否有效没有两个类型参数。
所以我想知道这种反向类型推断是否存在语法?还是一个解决方法?
答案 0 :(得分:4)
简单的答案是否定的。这最初不是关于类型推理 - 它是关于类型约束。您只能添加约束类型参数,该参数在同一声明中引入。所以这个:
public static void SomeMethod<U>(Action<T> d) where T: IGeneric<U>
无效,因为您尝试以T
为限制U
,而U
实际上是在方法声明中引入的T
。实际上,SomeClass
本身并不是任何地方的类型参数 - 但即使T
中的Action<Concrete>
是通用的,这也会失败。
在很多情况下类似,你可以通过非泛型类型的额外静态方法,通过类型推断创建泛型类型的实例 - 但具体通常是你有两个类型参数,你想明确指定一个。
需要注意的一点是,Action<IGeneric<object>>
只是而不是 Concrete
。例如,Action<Concrete>
可能会暴露Action<IGeneric<object>>
可能依赖的一些额外属性 - 但是如果给出IGeneric<object>
,您可以使用不同的实现来轻松调用SomeMethod
{1}}。您现有的Action<U>
尝试按特定Action<IGeneric<T>>
而不是U
对其进行排序 - 但此时使用操作相对较难。即使在类型推断有效的情况下,这很少(根据我的经验)是一种实用的方法。
一旦你改为一个真正协变的代表(假设你正在使用C#4),那么除非你关心using System;
interface IGeneric<T> {}
class SomeClass
{
public static void SomeMethod<T>(Func<IGeneric<T>> d) {}
}
class Concrete: IGeneric<object> {}
class Test
{
static void Main()
{
var d = default(Func<Concrete>);
// This compiles fine
SomeClass.SomeMethod(d);
}
}
,否则你可以简单地使用不同的签名:
{{1}}
答案 1 :(得分:2)
问题是你试图将Action<T>
视为T中的协变,但事实并非如此。事实上,它是逆变的。
例如,如果你有一个协变代表,就像这样。
delegate T CovariantCall<out T>();
你可以轻松地做你想要的事。
CovariantCall<IGeneric<object>> covariant = default(CovariantCall<Concrete>);
您的第一个示例未编译,因为您在方法的声明的类型参数列表中省略了T
。不过,这是一个更好的主意,并且它的工作原理是因为约束只验证并且不影响参数方差,但是您必须明确指定您要查找的参数并且无法推断它。
public static void SomeMethod<T, U>(Action<T> d) where T: IGeneric<U>
{
...
}
SomeClass.SomeMethod<Concrete, object>(default(Action<Concrete>));
C#类型推断能够有限制,这就是其中之一。如果没有明确指定类型,你就不能做你想要的。