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中(意思是TMethodTClass的超集)。

我在这里想要的是,TMethod应该是一个有效的类型,如果TClass继承或实现它,即TClass > TMethodTMethod : TClass也会这样做吗?

(其中一个答案指出TMethod : TClass要求TMethod可以从 TClass分配。我不确定这是否符合我的要求,但如果确实如此,请更详细地解释可从分配的含义,因为如果它有助于我,我可能误解了它…)

c#泛型约束:类的类型参数继承方法的类型参数

执行摘要:

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中赋值的地方调用这个方法

相关文章: