27

物品类别

public class Item
{
    public bool Check(int value) { ... }
}

具有泛型类型约束的基本抽象类

public abstract class ClassBase<TItem>
    where TItem : Item
{
    protected IList<TItem> items;

    public ClassBase(IEnumerable<TItem> items)
    {
        this.items = items.ToList();
    }    

    public abstract bool CheckAll(int value);
}

没有约束的继承类

public class MyClass<TItem> : ClassBase<TItem>
{
    public override bool CheckAll(int value)
    {
        bool result = true;
        foreach(TItem item in this.items)
        {
            if (!item.Check(value)) // this doesn't work
            {
                result = false;
                break;
            }
        }
        return result;
    }
}

我想知道为什么泛型类型约束不能继承?因为如果我的继承类继承自基类并传递其对基类有约束的泛型类型,则它自动意味着继承类中的泛型类型应该具有相同的约束,而无需显式定义它。不应该吗?

我做错了什么,理解错了,还是泛型类型约束真的不可继承?如果后者是真的,那世界上为什么会这样呢?

一点补充说明

为什么我认为在一个类上定义的泛型类型约束应该在子类上继承或强制执行?让我给你一些额外的代码,让它不那么明显。

假设我们拥有上述所有三个类。然后我们也有这个类:

public class DanteItem
{
    public string ConvertHellLevel(int value) { ... }
}

正如我们所看到的,这个类不继承自,Item因此它不能用作具体类ClassBase<DanteItem>(暂时忘记它是抽象的事实ClassBase。它也可以是一个常规类)。由于MyClass没有为其泛型类型定义任何约束,因此拥有MyClass<DanteItem>...

但。这就是为什么我认为泛型类型约束应该像成员泛型类型约束一样在继承的类上继承/强制执行,因为如果我们查看MyClass它的定义:

MyClass<T> : ClassBase<T>

T什么时候DanteItem我们可以看到它不能自动使用,MyClass因为它继承自ClassBase<T>并且DanteItem不满足其泛型类型约束。我可以说 **generic type onMyClass取决于ClassBase泛型类型约束,因为否则MyClass可以用任何类型实例化。但我们知道这是不可能的。

当我MyClass定义为:

public class MyClass<T> : ClassBase<Item>

在这种情况下, T 与基类的泛型类型没有任何关系,因此它独立于它。

这都是有点长的解释/推理。我可以简单地总结为:

如果我们不为其提供泛型类型约束,MyClass则隐含地暗示我们可以MyClass使用任何具体类型进行实例化。但我们知道这是不可能的,因为MyClass它继承自ClassBase并且具有泛型类型约束。

我希望这现在更有意义。

4

4 回答 4

43

另一个更新:

这个问题是我 2013 年 7 月博客的主题。谢谢你的好问题!

更新:

我对此进行了更多考虑,我认为问题在于您根本不想要继承。相反,您想要的是所有必须放置在类型参数上的约束,以便将该类型参数用作另一种类型的类型参数,以便自动推导并无形地添加到类型参数的声明中。 是的?

一些简化的例子:

class B<T> where T:C {}
class D<U> : B<U> {}

U 是在必须是 C 的上下文中使用的类型参数。因此,您认为编译器应该推断出这一点,并自动将 C 的约束放在 U 上。

那这个呢?

class B<T, U> where T : X where U : Y {}
class D<V> : B<V, V> {}

现在 V 是在必须同时是 X 和 Y 的上下文中使用的类型参数。因此,您认为编译器应该推断出这一点并自动在 V 上设置 X 和 Y 的约束。是吗?

那这个呢?

class B<T> where T : C<T> {}
class C<U> : B<D<U>> where U : IY<C<U>> {}
class D<V> : C<B<V>> where V : IZ<V> {}

我只是编造的,但我向你保证,这是一个完全合法的类型层次结构。请描述一个明确且一致的规则,该规则不会进入无限循环以确定 T、U 和 V 上的所有约束。不要忘记处理已知类型参数为引用类型且接口约束具有的情况协方差或逆变注释!此外,该算法必须具有无论 B、C 和 D 出现在源代码中的什么顺序,都能给出完全相同的结果的特性。

如果约束推断是您想要的功能,那么编译器必须能够处理这样的情况,并在不能处理时给出明确的错误消息。

基本类型有什么特别之处?为什么不完全实现该功能呢?

class B<T> where T : X {}
class D<V> { B<V> bv; }

V 是在必须可转换为 X 的上下文中使用的类型参数;因此编译器应该推断出这个事实并在 V 上设置 X 的约束。是吗?或没有?

为什么字段特别?那这个呢:

class B<T> { static public void M<U>(ref U u) where U : T {} }
class D<V> : B<int> { static V v; static public void Q() { M(ref v); } }

V 是在只能是 int 的上下文中使用的类型参数。因此,C# 编译器应该推断出这个事实并自动在 V 上放置一个 int 约束。

是的?不?

你知道这是怎么回事吗?它停在哪里?为了正确实现您想要的功能,编译器必须进行整个程序分析。

编译器不做这个级别的分析,因为那是本末倒置。当你构造一个泛型时,需要向编译器证明你已经满足了约束。编译器的工作不是弄清楚您要说什么并计算出哪些进一步的约束集满足原始约束。

出于类似的原因,编译器也不会尝试代表您自动推断接口中的方差注释。有关详细信息,请参阅我关于该主题的文章。

http://blogs.msdn.com/b/ericlippert/archive/2007/10/29/covariance-and-contravariance-in-c-part-seven-why-do-we-need-a-syntax-at-所有的.aspx


原答案:

我想知道为什么泛型类型约束不能继承?

只有成员被继承。约束不是成员。

如果我的继承类继承自基类并传递其对基类有约束的泛型类型,则自动意味着继承类中的泛型类型应具有相同的约束,而无需显式定义它。不应该吗?

您只是在断言某事应该如何,而没有提供任何解释为什么应该这样。向我们解释为什么您认为世界应该是这样的;有什么好处,有什么缺点,有什么成本

我做错了什么,理解错了,还是泛型类型约束真的不可继承?

通用约束不被继承。

如果后者是真的,那究竟为什么会这样呢?

默认情况下,功能“未实现”。我们不必提供实现某个功能的原因!在有人花钱实施之前,每个功能都不会实施。

现在,我赶紧指出泛型类型约束是在方法上继承的。方法是成员,成员是继承的,约束是方法的一部分(尽管不是其签名的一部分)。因此,约束在继承时与方法一起出现。当你说:

class B<T> 
{
    public virtual void M<U>() where U : T {}
}

class D<V> : B<IEnumerable<V>>
{
    public override void M<U>() {}
}

然后D<V>.M<U>继承约束并替换IEnumerable<V>T;因此约束是 U 必须可转换为IEnumerable<V>. 请注意,C# 不允许您重述约束。在我看来,这是一个错误的特征;为了清楚起见,我希望能够重申约束。

但是 D 没有从 B继承任何对T的约束;我不明白它怎么可能。M 是 B 的成员,并由 D 及其约束继承。但是 T 本来就不是 B 的成员,那么有什么可以继承的呢?

我真的完全不明白你想要什么功能。你能解释更多细节吗?

于 2011-12-22T16:15:27.657 回答
0

下面是这种行为的隐含性质导致与预期不同的行为的场景:

我认识到这种情况在设置数量上可能看起来很奢侈,但这只是这种行为可能导致问题的一个例子。软件应用程序可能很复杂,因此即使这种情况看起来很复杂,我也不会说这不可能发生。

在这个例子中,有一个 Operator 类实现了两个类似的接口:IMonitor 和 IProcessor。两者都有一个 start 方法和一个 IsStarted 属性,但 Operator 类中每个接口的行为是独立的。即在Operator 类中有一个_MonitorStarted 变量和一个_ProcessorStarted 变量。

MyClass<T>源自ClassBase<T>。ClassBase 对 T 有一个类型约束,它必须实现 IProcessor 接口,并且根据建议的行为 MyClass 继承该类型约束。

MyClass<T>有一个 Check 方法,该方法是在假设它可以从内部 IProcessor 对象中获取 IProcessor.IsStarted 属性的值的情况下构建的。

假设有人更改了 ClassBase 的实现,以移除 IProcessor 对泛型参数 T 的类型约束,并将其替换为 IMonitor 的类型约束。此代码将静默工作,但会产生不同的行为。原因是因为 Check 方法MyClass<T>现在调用的是 IMonitor.IsStarted 属性而不是 IProcessor.IsStarted 属性,即使 for 的代码MyClass<T>根本没有改变。

public interface IMonitor
{
    void Start();

    bool IsStarted { get; }
}

public interface IProcessor
{
    void Start();

    bool IsStarted { get; }
}

public class Operator : IMonitor, IProcessor
{
    #region IMonitor Members

    bool _MonitorStarted;

    void IMonitor.Start()
    {
        Console.WriteLine("IMonitor.Start");
        _MonitorStarted = true;
    }

    bool IMonitor.IsStarted
    {
        get { return _MonitorStarted; }
    }

    #endregion

    #region IProcessor Members

    bool _ProcessorStarted;

    void IProcessor.Start()
    {
        Console.WriteLine("IProcessor.Start");
        _ProcessorStarted = true;
    }

    bool IProcessor.IsStarted
    {
        get { return _ProcessorStarted; }
    }

    #endregion
}

public class ClassBase<T>
    where T : IProcessor
{
    protected T Inner { get; private set; }

    public ClassBase(T inner)
    {
        this.Inner = inner;
    }

    public void Start()
    {
        this.Inner.Start();
    }
}

public class MyClass<T> : ClassBase<T>
    //where T : IProcessor
{
    public MyClass(T inner) : base(inner) { }

    public bool Check()
    {
        // this code was written assuming that it is calling IProcessor.IsStarted
        return this.Inner.IsStarted;
    }
}

public static class Extensions
{
    public static void StartMonitoring(this IMonitor monitor)
    {
        monitor.Start();
    }

    public static void StartProcessing(this IProcessor processor)
    {
        processor.Start();
    }

}

class Program
{
    static void Main(string[] args)
    {
        var @operator = new Operator();

        @operator.StartMonitoring();

        var myClass = new MyClass<Operator>(@operator);

        var result = myClass.Check();

        // the value of result will be false if the type constraint on T in ClassBase<T> is where T : IProcessor
        // the value of result will be true if the type constraint on T in ClassBase<T> is where T : IMonitor
    }
}
于 2011-12-22T17:29:59.767 回答
-1

我认为你很困惑,因为你也在声明你的派生类TItem

如果您考虑一下,如果您使用的是这样的话Q

public class MyClass<Q> : BaseClass<Q>
{
 ...
}

那么如何确定Q是类型 item呢?

您还需要将约束添加到派生类 Generic Type 中

public class MyClass<Q> : BaseClass<Q> were Q : Item { ... } 
于 2011-12-22T16:07:15.380 回答
-2

因为 ClassBase 对他的模板有一个约束(应该是 typeof Item),所以您也必须将此约束添加到 MyClass 中。如果您不这样做,您可以创建 MyClass 的新实例,其中模板不是 Item 类型。创建基类时,它会失败。

[编辑] 嗯,现在重新阅读你的问题,我看到你的代码编译了吗?行。

好吧,我的 MyClass 你不知道 this.items 的基本类型,所以你不能调用 Check 方法。this.items 属于 IList 类型,在您的类中,未指定 TItem,这就是该类不理解 Check 方法的原因。

让我反驳您的问题,您为什么不想将约束添加到您的 MyClass 类?给定任何其他类类型作为此类的模板,将导致错误。为什么不通过添加约束来防止此错误,以免编译时失败。

于 2011-12-22T16:03:51.383 回答