代码之家  ›  专栏  ›  技术社区  ›  João Paiva

如何对继承的类型强制接口泛型规则?[复制品]

  •  0
  • João Paiva  · 技术社区  · 6 年前

    项目类别

    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<T> : ClassBase<T>
    

    什么时候? T DanteItem 我们可以看到它不能自动与 类名 因为它是从 ClassBase<T> 丹特尔 不满足其泛型类型约束。我可以说**泛型 类名 取决于 阶级基础 泛型类型约束,因为否则 类名 可以用任何类型实例化。但我们知道不可能。

    当然,当我 类名 定义为:

    public class MyClass<T> : ClassBase<Item>
    

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

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

    如果我们不在 类名 它暗示我们可以 类名 具有 任何混凝土类型 . 但我们知道这是不可能的,因为 类名 继承自 阶级基础 那个有一个泛型类型约束。

    我希望这现在更有意义。

    0 回复  |  直到 11 年前
        1
  •  41
  •   Eric Lippert    11 年前

    另一个更新:

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

    更新:

    我已经考虑过了问题是你不想 遗传 完全。相反,你想要的是 必须放置在类型参数上的所有约束,以使该类型参数用作另一类型中的类型参数,从而自动推导并无形地添加到类型参数的声明中。 对?

    一些简化示例:

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

    u是一个类型参数,用于必须是c的上下文中。因此,在您看来,编译器应该推断出这一点,并自动对u施加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的上下文中;因此编译器应该推断出这个事实,并对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-all.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没有继承 T 我不明白怎么可能。M是 成员 是B的,由D及其约束继承。但是t本来就不是b的成员,那么还有什么可以继承呢?

    我真的一点也不明白你想要什么样的功能。你能详细解释一下吗?

        2
  •  0
  •   Dr. Wily's Apprentice    13 年前

    下面是一个场景,在该场景中,此行为的隐式性质导致不同于预期的行为:

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

    在本例中,有一个运算符类实现了两个类似的接口:imonitor和iprocessor。两者都有start方法和isstarted属性,但operator类中每个接口的行为是独立的。即,在operator类中有一个monitorStarted变量和一个processorStarted变量。

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

    我的类别<t> 有一个check方法,该方法是在假设可以从内部iprocessor对象获取iprocessor.isstarted属性的值的情况下生成的。

    假设有人更改了classbase的实现,以删除泛型参数t上iprocessor的类型约束,并将其替换为imonitor的类型约束。此代码将默默工作,但会产生不同的行为。原因是签入方法 我的类别<t> 现在正在调用imonitor.isstarted属性而不是iprocessor.isstarted属性,即使 我的类别<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
        }
    }
    
        3
  •  -1
  •   msarchet    13 年前

    我觉得你很困惑,因为你声明你的派生类 TItem 也。

    如果你想一想如果你用的是 Q 而是这样。

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

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

    您还需要将约束添加到派生类泛型类型,以便

    public class MyClass<Q> : BaseClass<Q> were Q : Item { ... } 
    
        4
  •  -2
  •   Michiel van Vaardegem    13 年前

    因为classbase在他的模板上有一个约束(应该按项目类型),所以您也必须将这个约束添加到myclass。 如果不这样做,可以创建myClass的新实例,其中模板不是项的类型。创建基类时,它将失败。

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

    嗯,我是myClass,你不知道this.items的基类型,所以你不能调用check方法。 this.items属于IList类型,在您的类中,没有指定titem,这就是类不理解check方法的原因。

    让我回答您的问题,为什么不将约束添加到MyClass类中?如果将任何其他类类型作为该类的模板,则会导致错误。为什么不通过添加一个约束来防止这个错误,这样它将使compiletime失败。