【问题标题】:How to use Inheritance when using Generic Constraints使用泛型约束时如何使用继承
【发布时间】:2010-09-02 12:12:25
【问题描述】:

在尝试实现允许继承的库并希望有人能提供帮助时,我遇到了一些通用约束问题。

我正在尝试构建一个具有 3 种风格的类库,每种风格都建立在另一个之上。对我来说,这似乎是一个使用泛型的绝佳机会,因为我不能完全通过纯粹的继承来做我想做的事。代码如下(这应该直接粘贴到 VS 中),之后有一些解释:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    #region Base Classes

    public class GenericElement { }

    /// <summary>Visit to a GenericElement</summary>
    public class Generic_Visit<E> where E : GenericElement
    {
        public E Element { get; set; }
    }

    /// <summary>Collection of Visits</summary>
    public class Generic_Route<V, E>
        where V : Generic_Visit<E>
        where E : GenericElement
    {
        public List<V> Visits { get; set; }
        public Double Distance { get; set; }
    }

    /// <summary>Collection of Routes</summary>
    public class Generic_Solution<R, V, E>
        where R : Generic_Route<V, E>
        where V : Generic_Visit<E>
        where E : GenericElement
    {
        public List<R> Routes { get; set; }

        public Double Distance
        {
            get
            {
                return this.Routes.Select(r => r.Distance).Sum();
            }
        }
    }

    #endregion

    #region TSP Classes

    public class Concrete_TSPNode : GenericElement { }

    public abstract class Generic_TSPVisit<E> : Generic_Visit<E>
        where E : Concrete_TSPNode
    {
        public Double Time { get; set; }
    }

    public abstract class Generic_TSPRoute<V, E> : Generic_Route<V, E>
        where V : Concrete_TSPVisit
        where E : Concrete_TSPNode
    {
        public Double Time
        {
            get
            {
                return this.Visits.Select(v => v.Time).Sum();
            }
        }
    }

    public abstract class Generic_TSPSolution<R, V, E> : Generic_Solution<R, V, E>
        where R : Concrete_TSPRoute
        where V : Concrete_TSPVisit
        where E : Concrete_TSPNode
    {
        public Double Time
        {
            get
            {
                return this.Routes.Select(r => r.Time).Sum();
            }
        }
    }

    public class Concrete_TSPVisit : Generic_TSPVisit<Concrete_TSPNode> { }

    public class Concrete_TSPRoute : Generic_TSPRoute<Concrete_TSPVisit, Concrete_TSPNode> { }

    public class Concrete_TSPSolution : Generic_TSPSolution<Concrete_TSPRoute, Concrete_TSPVisit, Concrete_TSPNode> { }

    #endregion

    #region VRP

    public class Concrete_VRPNode : Concrete_TSPNode { }

    public abstract class Generic_VRPVisit<E> : Generic_TSPVisit<E> where E : Concrete_VRPNode
    {
        public Double Capacity { get; set; }
    }

    public abstract class Generic_VRPRoute<V, E> : Generic_TSPRoute<V, E>
        where V : Concrete_VRPVisit
        where E : Concrete_VRPNode
    {
        public Double Capacity
        {
            get
            {
                return this.Visits.Select(v => v.Capacity).Sum();
            }
        }
    }

    public abstract class G_VRPSolution<R, V, E> : Generic_TSPSolution<R, V, E>
        where R : Concrete_VRPRoute
        where V : Concrete_VRPVisit
        where E : Concrete_VRPNode
    {
        public Double Capacity
        {
            get
            {
                return this.Routes.Select(r => r.Capacity).Sum();
            }
        }
    }

    public class Concrete_VRPVisit : Generic_VRPVisit<Concrete_VRPNode> { }

    public class Concrete_VRPRoute : Generic_VRPRoute<Concrete_VRPVisit, Concrete_VRPNode> { }

    public class Concrete_VRPSolution : Generic_TSPSolution<Concrete_VRPRoute, Concrete_VRPVisit, Concrete_VRPNode> { }

    #endregion
}

代码背后的想法是有一组使用泛型公开属性的基类。例如,这允许我拥有强类型集合。

3 个阶段中有 2 个建立在这些阶段之上,示例中的 TSP 和 VRP 有 4 个具体类(这些是使用类库的开发人员应该与之交互的,因为通用约束有点疯狂) - 元素、访问、路线和解决方案。

对于 TSP 和 VRP,还有一些以 Generic 为前缀的类。这些允许我想要的继承,因为它公开了通用类型。如果我不使用这些(例如 Concrete_VRPRoute 继承 Concrete_TSPRoute),那么我必须继续转换 Visits 集合返回的项目类型以获取容量属性。

我相当有信心所有类型都正确排列,但是当我尝试构建时出现以下错误,我真的不知道如何解决它们。

错误 1 ​​类型 'V' 不能用作 泛型类型中的类型参数“V” 或方法“Test.Generic_Route”。 没有隐式引用 从“V”转换为 'Test.Generic_Visit'。

错误 2 类型“V”不能用作类型 泛型类型中的参数“V”或 方法“Test.Generic_Solution”。 没有隐式引用 从“V”转换为 'Test.Generic_Visit'。

错误 3 类型“R”不能用作类型 泛型类型中的参数“R”或 方法“Test.Generic_Solution”。 没有隐式引用 从“R”转换为 'Test.Generic_Route'

错误 4 类型 'V' 不能用作 泛型类型中的类型参数“V” 或方法 'Test.Generic_TSPRoute'。有 没有隐式引用转换 “V”到“Test.Concrete_TSPVisit”。

错误 5 类型 'V' 不能用作 泛型类型中的类型参数“V” 或方法 'Test.Generic_TSPSolution'。 没有隐式引用 从“V”转换为 'Test.Concrete_TSPVisit'。

错误 6 类型“R”不能用作类型 泛型类型中的参数“R”或 方法 'Test.Generic_TSPSolution'。 没有隐式引用 从“R”转换为 'Test.Concrete_TSPRoute'。

错误 7 类型“Test.Concrete_VRPVisit”不能 用作类型参数“V” 泛型类型或方法 'Test.Generic_TSPSolution'。 没有隐式引用 从转换 'Test.Concrete_VRPVisit' 到 'Test.Concrete_TSPVisit'。

错误 8 类型“Test.Concrete_VRPRoute”不能 用作类型参数“R” 泛型类型或方法 'Test.Generic_TSPSolution'。 没有隐式引用 从转换 'Test.Concrete_VRPRoute' 到 'Test.Concrete_TSPRoute'。'Test.Concrete_TSPRoute'。

【问题讨论】:

    标签: c# generics inheritance


    【解决方案1】:

    这是一块普通的蛋糕。您需要根据自身定义泛型类。递归泛型定义。

    基类:

    public class Generic_Element<E>
        where E : Generic_Element<E>
    {
    }
    
    /// <summary>Visit to a Generic_Element</summary>
    public class Generic_Visit<V, E>
        where V : Generic_Visit<V, E>
        where E : Generic_Element<E>
    {
        public E Element { get; set; }
    }
    
    /// <summary>Collection of Visits</summary>
    public class Generic_Route<R, V, E>
        where R : Generic_Route<R, V, E>
        where V : Generic_Visit<V, E>
        where E : Generic_Element<E>
    {
        public List<V> Visits { get; set; }
        public Double Distance { get; set; }
    }
    
    /// <summary>Collection of Routes</summary>
    public class Generic_Solution<S, R, V, E>
        where S : Generic_Solution<S, R, V, E>
        where R : Generic_Route<R, V, E>
        where V : Generic_Visit<V, E>
        where E : Generic_Element<E>
    {
        public List<R> Routes { get; set; }
    
        public Double Distance
        {
            get
            {
                return this.Routes.Select(r => r.Distance).Sum();
            }
        }
    }
    

    TSP 类:

    public class Generic_Tsp_Element<E> : Generic_Element<E>
    where E : Generic_Tsp_Element<E>
    {
    }
    
    /// <summary>Visit to a Generic_Element</summary>
    public class Generic_Tsp_Visit<V, E> : Generic_Visit<V, E>
        where V : Generic_Tsp_Visit<V, E>
        where E : Generic_Tsp_Element<E>
    {
        public Double Time { get; set; }
    }
    
    /// <summary>Collection of Visits</summary>
    public class Generic_Tsp_Route<R, V, E> : Generic_Route<R, V, E>
        where R : Generic_Tsp_Route<R, V, E>
        where V : Generic_Tsp_Visit<V, E>
        where E : Generic_Tsp_Element<E>
    {
        public Double Time
        {
            get
            {
                return this.Visits.Select(v => v.Time).Sum();
            }
        }
    }
    
    /// <summary>Collection of Routes</summary>
    public class Generic_Tsp_Solution<S, R, V, E> : Generic_Solution<S, R, V, E>
        where S : Generic_Tsp_Solution<S, R, V, E>
        where R : Generic_Tsp_Route<R, V, E>
        where V : Generic_Tsp_Visit<V, E>
        where E : Generic_Tsp_Element<E>
    {
        public Double Time
        {
            get
            {
                return this.Routes.Select(r => r.Time).Sum();
            }
        }
    }
    
    public class Concrete_Tsp_Element : Generic_Tsp_Element<Concrete_Tsp_Element> { }
    
    public class Concrete_Tsp_Visit : Generic_Tsp_Visit<Concrete_Tsp_Visit, Concrete_Tsp_Element> { }
    
    public class Concrete_Tsp_Route : Generic_Tsp_Route<Concrete_Tsp_Route, Concrete_Tsp_Visit, Concrete_Tsp_Element> { }
    
    public class Concrete_Tsp_Solution : Generic_Tsp_Solution<Concrete_Tsp_Solution, Concrete_Tsp_Route, Concrete_Tsp_Visit, Concrete_Tsp_Element> { }
    

    VRP 类:

    public class Generic_Vrp_Element<E> : Generic_Element<E>
    where E : Generic_Vrp_Element<E>
    {
    }
    
    /// <summary>Visit to a Generic_Element</summary>
    public class Generic_Vrp_Visit<V, E> : Generic_Visit<V, E>
        where V : Generic_Vrp_Visit<V, E>
        where E : Generic_Vrp_Element<E>
    {
        public Double Capacity { get; set; }
    }
    
    /// <summary>Collection of Visits</summary>
    public class Generic_Vrp_Route<R, V, E> : Generic_Route<R, V, E>
        where R : Generic_Vrp_Route<R, V, E>
        where V : Generic_Vrp_Visit<V, E>
        where E : Generic_Vrp_Element<E>
    {
        public Double Capacity
        {
            get
            {
                return this.Visits.Select(v => v.Capacity).Sum();
            }
        }
    }
    
    /// <summary>Collection of Routes</summary>
    public class Generic_Vrp_Solution<S, R, V, E> : Generic_Solution<S, R, V, E>
        where S : Generic_Vrp_Solution<S, R, V, E>
        where R : Generic_Vrp_Route<R, V, E>
        where V : Generic_Vrp_Visit<V, E>
        where E : Generic_Vrp_Element<E>
    {
        public Double Capacity
        {
            get
            {
                return this.Routes.Select(r => r.Capacity).Sum();
            }
        }
    }
    
    public class Concrete_Vrp_Element : Generic_Vrp_Element<Concrete_Vrp_Element> { }
    
    public class Concrete_Vrp_Visit : Generic_Vrp_Visit<Concrete_Vrp_Visit, Concrete_Vrp_Element> { }
    
    public class Concrete_Vrp_Route : Generic_Vrp_Route<Concrete_Vrp_Route, Concrete_Vrp_Visit, Concrete_Vrp_Element> { }
    
    public class Concrete_Vrp_Solution : Generic_Vrp_Solution<Concrete_Vrp_Solution, Concrete_Vrp_Route, Concrete_Vrp_Visit, Concrete_Vrp_Element> { }
    

    最终的结果是可以像这样使用的非泛型具体类:

    var e = new Concrete_Tsp_Element();
    var v = new Concrete_Tsp_Visit();
    v.Element = e;
    v.Time = 0.5;
    var r = new Concrete_Tsp_Route();
    r.Visits = new List<Concrete_Tsp_Visit>(new[] { v });
    r.Distance = 2.1;
    var s = new Concrete_Tsp_Solution();
    s.Routes = new List<Concrete_Tsp_Route>(new[] { r });
    Console.WriteLine(s.Distance);
    Console.WriteLine(s.Time);
    Console.ReadLine();
    

    享受吧! 享受吧!

    【讨论】:

    • 看起来很有趣。我明天试一试,让你知道结果:)
    • 天哪……这确实有效,不是吗?我很感动。说到 C#,我也不容易给人留下深刻印象! — 你知道有什么好的网站或博客文章讨论了这种递归泛型类型的策略以及何时使用它们吗?我想为它建立一种直觉,以便我知道在适当的时候使用它。
    • @Timwi - 这就像一种享受 - 我只是在发布之前对其进行了编码,以确保我的语法正确。递归泛型是 GoF Bridge 模式的一种形式。我一直使用这种技术来创建继承公共代码的强绑定类型 - 它特别适用于实现 MVVM 代码,我想让我的 ViewModel 被约束到 Model 并且 View 被约束到 ViewModel。
    • @Ian - 相信我,它有效。我在发布之前测试了我的代码。 :-)
    • @Enigmativity:我试图删除一些通用约束,但它似乎仍然可以很好地编译,其中大约一半被删除。你有什么要为你的辩护说的? :)))
    【解决方案2】:

    好的,让我们检查第一个。错误是:

    类型“V”不能用作泛型类型或方法“Test.Generic_Route”中的类型参数“V”。没有从“V”到“Test.Generic_Visit”的隐式引用转换。

    它抱怨这个声明:

    public abstract class Generic_TSPRoute<V, E> : Generic_Route<V, E>
        where V : Concrete_TSPVisit
        where E : Concrete_TSPNode
    

    这确立了两个定义:

    • VConcrete_TSPVisit(或它的后代)

    • EConcrete_TSPNode(或它的后代)

    现在让我们看看Generic_Route&lt;V, E&gt; 让我们输入了什么:

    public class Generic_Route<V, E>
        where V : Generic_Visit<E>
        where E : GenericElement
    

    第二个约束很好,因为Concrete_TSPNodeGenericElement。第一个是有问题的:记住EConcrete_TSPNode 或它的后代,因此Generic_Visit&lt;E&gt; 可能是:

    • Generic_Visit&lt;Concrete_TSPNode&gt;

    • Generic_Visit&lt;some subclass of Concrete_TSPNode&gt;

    不过,我们之前也知道VConcrete_TSPVisit(或它的后代)。

    • Concrete_TSPVisit 继承自 Generic_TSPVisit&lt;Concrete_TSPNode&gt;

    • Generic_TSPVisit&lt;Concrete_TSPNode&gt; 继承自 Generic_Visit&lt;Concrete_TSPNode&gt;

    注意到什么了吗?这要求它是Generic_Visit&lt;Concrete_TSPNode&gt;。强调不允许Generic_Visit&lt;some subclass of Concrete_TSPNode&gt;

    换句话说,想象我这样写:

    var route = new Generic_TSPRoute<Concrete_TSPVisit, Concrete_TSPNode_Subclass>();
    

    根据您的层次结构,Concrete_TSPVisitGeneric_Visit&lt;Concrete_TSPNode&gt;,因此具有如下所示的属性

    public Concrete_TSPNode Element { get; set; }
    

    如果我从这个属性中检索一个值,它只能保证是Concrete_TSPNode,但不一定是Concrete_TSPNode_Subclass

    编辑:

    我会留下这个答案,因为它解释了编译器错误的原因,但 Enigmativity 的答案实际上提供了问题的解决方案。

    【讨论】:

    • @Timwi:感谢您的全面回答。这确实帮助我看到了问题(我从来没有完全理解变异的味道,我应该更仔细地阅读 Eric Lipperts 的博客)。你能解释一下关于只读的“setter/getter”问题吗?我可能遗漏了一些明显的东西......但不明白为什么使用界面会限制我只读?
    • @Ian:使用界面并不限制你是只读的。使用 co-variance 可以。例如,IEnumerable&lt;string&gt; 可以兼作IEnumerable&lt;object&gt;,因为IEnumerable&lt;T&gt;T 中是协变的,这可能是因为它是只读的(T 仅作为返回类型出现)。 IComparer&lt;object&gt; 可以兼作 IComparer&lt;string&gt;,因为它在 T 中是逆变的,这可能是因为它是只写的(T 仅作为参数类型出现)。
    • @Ian: P.S.当Generic_Visit&lt;E&gt; 同时具有 setter 和 getter 时,我只是在答案中添加了一些内容来解释问题。
    • @Timwi:好的,泥开始变清一点了。我会做一些阅读。看起来我无法通过它的声音来完成这个工作......?
    • 不需要协方差或逆变。 .NET 2.0 中引入的泛型工作得很好。你只需要一个递归的泛型定义。 stackoverflow.com/questions/3626937/… :-)
    【解决方案3】:

    这与 Enigmativity 的答案相同,但删除了所有冗余约束。这仍然可以编译,它没有任何通用递归,据我所知,它仍然是所需的类型安全。谜团,我错过了什么? :)

    public class Generic_Element { }
    
    public class Generic_Visit<E>
    {
        public E Element { get; set; }
    }
    
    /// <summary>Collection of Visits</summary>
    public class Generic_Route<V>
    {
        public List<V> Visits { get; set; }
        public Double Distance { get; set; }
    }
    
    /// <summary>Collection of Routes</summary>
    public class Generic_Solution<R, V>
        where R : Generic_Route<V>
    {
        public List<R> Routes { get; set; }
    
        public Double Distance
        {
            get
            {
                return this.Routes.Select(r => r.Distance).Sum();
            }
        }
    }
    
    public class Generic_Tsp_Element : Generic_Element
    {
    }
    
    /// <summary>Visit to a Generic_Element</summary>
    public class Generic_Tsp_Visit<E> : Generic_Visit<E>
    {
        public Double Time { get; set; }
    }
    
    /// <summary>Collection of Visits</summary>
    public class Generic_Tsp_Route<V, E> : Generic_Route<V>
        where V : Generic_Tsp_Visit<E>
    {
        public Double Time
        {
            get
            {
                return this.Visits.Select(v => v.Time).Sum();
            }
        }
    }
    
    /// <summary>Collection of Routes</summary>
    public class Generic_Tsp_Solution<R, V, E> : Generic_Solution<R, V>
        where R : Generic_Tsp_Route<V, E>
        where V : Generic_Tsp_Visit<E>
    {
        public Double Time
        {
            get
            {
                return this.Routes.Select(r => r.Time).Sum();
            }
        }
    }
    
    public class Concrete_Tsp_Element : Generic_Tsp_Element { }
    
    public class Concrete_Tsp_Visit : Generic_Tsp_Visit<Concrete_Tsp_Element> { }
    
    public class Concrete_Tsp_Route : Generic_Tsp_Route<Concrete_Tsp_Visit, Concrete_Tsp_Element> { }
    
    public class Concrete_Tsp_Solution : Generic_Tsp_Solution<Concrete_Tsp_Route, Concrete_Tsp_Visit, Concrete_Tsp_Element> { }
    
    public class Generic_Vrp_Element : Generic_Element
    {
    }
    
    /// <summary>Visit to a Generic_Element</summary>
    public class Generic_Vrp_Visit<V, E> : Generic_Visit<E>
    {
        public Double Capacity { get; set; }
    }
    
    /// <summary>Collection of Visits</summary>
    public class Generic_Vrp_Route<R, V, E> : Generic_Route<V>
        where V : Generic_Vrp_Visit<V, E>
    {
        public Double Capacity
        {
            get
            {
                return this.Visits.Select(v => v.Capacity).Sum();
            }
        }
    }
    
    /// <summary>Collection of Routes</summary>
    public class Generic_Vrp_Solution<S, R, V, E> : Generic_Solution<R, V>
        where R : Generic_Vrp_Route<R, V, E>
        where V : Generic_Vrp_Visit<V, E>
    {
        public Double Capacity
        {
            get
            {
                return this.Routes.Select(r => r.Capacity).Sum();
            }
        }
    }
    
    public class Concrete_Vrp_Element : Generic_Vrp_Element { }
    
    public class Concrete_Vrp_Visit : Generic_Vrp_Visit<Concrete_Vrp_Visit, Concrete_Vrp_Element> { }
    
    public class Concrete_Vrp_Route : Generic_Vrp_Route<Concrete_Vrp_Route, Concrete_Vrp_Visit, Concrete_Vrp_Element> { }
    
    public class Concrete_Vrp_Solution : Generic_Vrp_Solution<Concrete_Vrp_Solution, Concrete_Vrp_Route, Concrete_Vrp_Visit, Concrete_Vrp_Element> { }
    

    【讨论】:

    • 您的答案不限制元素类型E。我可以使用我喜欢的任何类型的E 创建类。我的解决方案对其进行了限制,因此它必须从Generic_Element&lt;E&gt; 继承,我认为这是 Ian 想要的。所以我的约束不是冗余的。 :-p
    • @Enigmativity:我知道,那又怎样。如果需要,您仍然可以在Generic_Visit&lt;E&gt; 上向E 添加约束,并且它仍然可以编译。关键是我看不到你的 recursive 约束添加了什么。
    • 您的解决方案还允许您在Generic_Vrp_Solution&lt;S, R, V, E&gt; 类型中为S 泛型参数使用任何类型。通过删除您拥有的约束,好吧,删除了约束。因此,例如,如果您希望解决方案类型返回自身,则需要使用完整的约束集。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-04
    • 1970-01-01
    • 1970-01-01
    • 2015-04-26
    • 1970-01-01
    相关资源
    最近更新 更多