为什么通用类型约束不可继承/分层实施

时间:2011-12-22 15:56:06

标签: c# inheritance generic-constraints

项目类

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>

TDanteItem时,我们可以看到它自动无法与MyClass一起使用,因为它继承自ClassBase<T>DanteItem不会实现其泛型类型约束。我可以说MyClass上的**泛型类型取决于ClassBase泛型类型约束,因为否则MyClass可以用任何类型实例化。但我们知道它不可能。

当我将MyClass定义为:

时,情况会有所不同
public class MyClass<T> : ClassBase<Item>

在这种情况下,T与基类'泛型类型无关,因此它与它无关。

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

  

如果我们不在MyClass上提供泛型类型约束,则隐含意味着我们可以使用任何具体类型来实例化MyClass。但是我们知道这是不可能的,因为MyClass是从ClassBase继承的,而且它有一个泛型类型约束。

我希望现在这更有意义。

4 个答案:

答案 0 :(得分:41)

另一个更新:

这个问题是the subject of my blog in July 2013。谢谢你提出的好问题!

更新:

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

一些简化的例子:

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

U是一个类型参数,在必须为C的上下文中使用。因此,在您看来,编译器应该推导出并自动在C上设置C的约束。

这个怎么样?

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

现在V是一个在上下文中使用的类型参数,它必须是X和Y.因此在您看来,编译器应该推导出并自动将X和Y的约束放在V上。是吗?

这个怎么样?

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的上下文中使用的类型参数;因此,编译器应该推导出这个事实,并在X上设置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-all.aspx


原始答案:

  

我想知道为什么通用类型约束不可继承?

仅继承成员。约束不是成员。

  

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

你只是断言应该是什么,而不提供为什么应该是这样的任何解释。向我们解释为什么你认为世界应该是这样的;什么是的好处以及缺点是什么以及成本是什么?

  

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

不继承通用约束。

  

如果后者是真的,世界上为什么会这样?

功能未被实施&#34;默认情况下。我们不必提供实施功能的原因! 除非有人花钱实施,否则每个功能都不会实现。

现在,我必须注意,方法上继承了泛型类型约束 。方法是成员,成员是继承的,约束是方法的一部分(虽然不是签名的一部分)。因此约束随着继承的方法而出现。当你说:

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的成员,那么继承什么呢?

我真的根本不了解你想要的功能。你能解释一下更多细节吗?

答案 1 :(得分: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的实现,以删除泛型参数T上的IProcessor的类型约束,并将其替换为IMonitor的类型约束。此代码将默默工作,但会产生不同的行为。原因是因为MyClass<T>中的Check方法现在正在调用IMonitor.IsStarted属性而不是IProcessor.IsStarted属性,即使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
    }
}

答案 2 :(得分:-1)

我认为你很困惑,因为你也要宣布你使用TItem派生班级。

如果您考虑使用Q,请考虑一下。

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

那么如何确定Q属于item类型?

您需要将约束添加到派生类Generic Type以及

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

答案 3 :(得分:-2)

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

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

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

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