C#泛型约束:类上的类型参数继承方法的类型参数?

时间:2011-07-14 15:58:35

标签: c# generics

我有一个泛型类,我在其上定义了一个方法,该方法应该接受另一种类型的参数,但前提是另一种类型实现类的类型参数。但是,这不会编译:

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 可以从 {{1}}分配。我不确定这是否符合我的要求,但是如果确实如此,请更详细地解释可从分配的意思,因为如果它对我有帮助,我可能会误解它......)

4 个答案:

答案 0 :(得分:8)

执行摘要:

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

答案 1 :(得分:4)

你无法做你在C#中要做的事情。约束本质上指定了必须具有的共同可指定点,并且约束TMethod:TClass将意味着您知道TMethod至少可以被视为TClass。

将TMethod约束为“在另一种类型的继承结构中的某个地方”的问题在于,就约束条件而言,现在没有好的方法来确定所给出的基本类型。您是否正在使用由完全不同类型的三个级别的基类实现的特定接口?你在基地上路过吗?您是否正在传递一个不相关的后代类,其基础恰好落入对象的继承结构中?对象是你得到的最接近所有可能组合的对象,在这一点上你也可以忘记使用泛型并直接使用Object,因为泛型提供的所有类型安全性好处都被抛到了窗外。 / p>

反转也会破坏多态性的目的。如果方法签名需要A类,而我的类型从A类下降,为什么我不允许将其作为A传递?如果反向为真并且方法签名可能需要B的任何基类并指定B,那么如果您能够传入A,则无法合理地期望B的任何功能都可用。

答案 2 :(得分:0)

您不能这样做,因为在方法类型签名中未定义TClassTClass是在类级别定义的,因此您无法在方法级别对其进行约束。

...试

class GenericClass<TClass>
{
    public void DoSomething<TMethod>(TMethod input) where TMethod : TClass
    {
        // ...
    }
}

这就是说,允许调用此方法,其中TMethod可从TClass分配。

答案 3 :(得分:0)

我认为,最接近的是传递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实现的接口类型。这些也可以明确排除。