c#泛型约束:类的类型参数继承方法的类型参数
本文关键字:类型参数 方法 继承 泛型 约束 | 更新日期: 2023-09-27 18:02:11
我有一个泛型类,我在其上定义了一个方法,该方法应该接受另一种类型的参数,但前提是另一种类型实现了该类的类型参数。但是,这不会编译:
class GenericClass<TClass>
{
public void DoSomething<TMethod>(TMethod input) where TClass : TMethod
{
// ...
}
}
我在方法的约束中得到TClass
上的编译器错误。还有别的方法来表示吗?
澄清:
在我的印象中,TMethod : TClass
意味着TMethod
必须继承或实现TClass
(取决于TClass
是具体类型还是接口)。在另一个稍微非常规的符号TMethod > TClass
中(意思是TMethod
是TClass
的超集)。
我在这里想要的是,TMethod
应该是一个有效的类型,如果TClass
继承或实现它,即TClass > TMethod
。TMethod : TClass
也会这样做吗?
(其中一个答案指出TMethod : TClass
要求TMethod
可以从 TClass
中分配。我不确定这是否符合我的要求,但如果确实如此,请更详细地解释可从分配的含义,因为如果它有助于我,我可能误解了它…)
执行摘要:
Chris Hannon的回答基本上是正确的。然而,有一些涉及接口和扩展方法的狡猾技巧可能会得到你想要的。过度细节:
我们偶尔会被要求使用这种约束,其他语言也有类似的约束。Java和Scala都有"这个必须是那个的超类型"的说法。正如其他人指出的那样,这种约束既不能在c#类型系统中表示,也不能在底层CLR类型系统中表示。
您可能需要这种类型的方差的原因是在这样的情况下:
class ImmutableStack<T>
{
public static ImmutableStack<T> Empty { get { return empty; } }
private static ImmutableStack<T> empty = new ImmutableStack<T>();
private T head;
private ImmutableStack<T> tail;
public ImmutableStack<T> Pop()
{
if (this == empty) throw new Exception();
return this.tail;
}
public ImmutableStack<N> Push(N value) where T : N // not legal
{
var result = new ImmutableStack<N>();
result.head = value;
result.tail = this; // also not legal
return result;
}
}
现在你可以这样做:
Tiger tony = new Tiger();
Elephant dumbo = new Elephant();
ImmutableStack<Tiger> tigers = ImmutableStack<Tiger>.Empty;
tigers = tigers.Push<Tiger>(tony);
ImmutableStack<Mammal> mammals = tigers.Push<Mammal>(dumbo);
嘿,现在你可以把一头大象推到一堆老虎上,它就神奇地变成了一堆哺乳动物!
这在c#中是不可能的,原因有两个:第一,因为没有这样的泛型约束,第二,因为不可变类类型不是协变的。(尾部的赋值需要协方差)
然而,如果你是鬼鬼祟祟的,在c#中可以做到这一点。您可以通过将所有内容表示为协变接口而不是类来解决协变问题。您可以通过将有问题的方法移出类并使其成为扩展方法来解决"无此约束"问题!我在这里给出一个示例:
http://blogs.msdn.com/b/ericlippert/archive/2007/12/06/immutability-in-c-part-three-a-covariant-immutable-stack.aspx你不能在c#中做你想做的事情。约束本质上指定了一个必须具有的公共可分配点,并且约束TMethod: TClass意味着您知道TMethod至少可以被视为TClass。
将TMethod约束为"在另一个类型的继承结构中的某个地方"的问题是,就约束而言,您现在没有好的方法来确定给定的基本类型。您是否使用了由基类在完全不同类型的三层深处实现的特定接口?是否传入基类?您是否正在传递一个不相关的后代类,其基恰好属于对象的继承结构?Object是你能找到的最接近匹配所有可能组合的对象,在这种情况下,你还不如忘记使用泛型而直接使用Object,因为泛型提供的所有类型安全好处都被抛在了窗外。
反过来也会破坏多态性的目的。如果一个方法签名需要类a,而我的类型继承自类a,为什么不允许我将它作为a传入呢?如果反过来是真的,并且方法签名可能期望B的任何基类并指定B,那么如果您能够传入a,您就不能合理地期望B的任何功能可用。
我认为,最接近的方法是传递Object并执行继承关系的运行时检查。
class GenericClass<TClass>
{
public void DoSomething(Object input)
{
if (input == null) throw new ArgumentNullException("input");
if (!input.GetType().IsAssignableFrom(typeof(TClass)))
throw new ArgumentException("Input type must be assignable from " + typeof(TClass).ToString(), "input");
// ...
}
}
注意,这将允许Object类型的输入,也允许TClass实现的接口类型的输入(在透明远程代理的极少数情况下)。这些也可以显式排除。
您不能这样做,因为TClass
没有在方法类型签名中定义。TClass
是在类级别定义的,因此你不能在方法级别约束它。
试试……
class GenericClass<TClass>
{
public void DoSomething<TMethod>(TMethod input) where TMethod : TClass
{
// ...
}
}
这就是说,允许在TMethod可以从TClass中赋值的地方调用这个方法