2017-07-25 37 views
0

我有一个由几个“水平”的元素组成的应用程序。有些元素是0..N子元素的父元素,它们本身是0..N其他元素的父元素。顶级类型没有父级,底级元素没有子级。换句话说,让三种类型:A,B和C.A的一个实例是B的多个实例的父亲,它们本身是C的多个实例的父亲。每个实例也有一个(强类型的)参考其父母。与看起来像奇怪的递归模板模式的东西混淆

我有几个父类中的方法是一样的,比如AddChild,RemoveChild,GetChildIndex等。我想为所有父类有一个基类,以便不为每个父类型重复这些方法。

这个想法是,当从父基类派生时,必须根据父类的类型提供子类型。

到目前为止,我想出了这个过于复杂的设计:

public interface IChild<TParent> where TParent : ParentBase<IChild<TParent>> 
{ 
    TParent Parent { get; set; } 
} 

public class ParentBase<TChild> where TChild : IChild<ParentBase<TChild>> 
{ 
    public List<TChild> Children; 
} 

public class A : ParentBase<B> 
{ 
} 

public class B : ParentBase<C>, IChild<A> 
{ 
} 

public class C : IChild<B> 
{ 
} 

但我发现了编译错误:

Error CS0311 The type 'TemplateTest.IChild<TParent>' cannot be used as type parameter 'TChild' in the generic type or method 'ParentBase<TChild>'. There is no implicit reference conversion from 'TemplateTest.IChild<TParent>' to 'TemplateTest.IChild<TemplateTest.ParentBase<TemplateTest.IChild<TParent>>>'. Kbd2 C:\dev\Kbd2\TemplateTest.cs 6 Active 
Error CS0311 The type 'TemplateTest.ParentBase<TChild>' cannot be used as type parameter 'TParent' in the generic type or method 'IChild<TParent>'. There is no implicit reference conversion from 'TemplateTest.ParentBase<TChild>' to 'TemplateTest.ParentBase<TemplateTest.IChild<TemplateTest.ParentBase<TChild>>>'. Kbd2 C:\dev\Kbd2\TemplateTest.cs 11 Active 
Error CS0311 The type 'TemplateTest.B' cannot be used as type parameter 'TChild' in the generic type or method 'ParentBase<TChild>'. There is no implicit reference conversion from 'TemplateTest.B' to 'TemplateTest.IChild<TemplateTest.ParentBase<TemplateTest.B>>'. Kbd2 C:\dev\Kbd2\TemplateTest.cs 16 Active 
Error CS0311 The type 'TemplateTest.C' cannot be used as type parameter 'TChild' in the generic type or method 'ParentBase<TChild>'. There is no implicit reference conversion from 'TemplateTest.C' to 'TemplateTest.IChild<TemplateTest.ParentBase<TemplateTest.C>>'. Kbd2 C:\dev\Kbd2\TemplateTest.cs 20 Active 
Error CS1721 Class 'B' cannot have multiple base classes: 'ParentBase<C>' and 'IChild<A>' Kbd2 C:\dev\Kbd2\TemplateTest.cs 20 Active 
Error CS0311 The type 'TemplateTest.B' cannot be used as type parameter 'TParent' in the generic type or method 'IChild<TParent>'. There is no implicit reference conversion from 'TemplateTest.B' to 'TemplateTest.ParentBase<TemplateTest.IChild<TemplateTest.B>>'. Kbd2 C:\dev\Kbd2\TemplateTest.cs 24 Active 

我甚至不知道这是否可以编译或不因为课程相互依赖。难道我做错了什么 ?谢谢。

编辑:增加了未实现的方法和更新的错误列表。

EDIT:通过使子接口的基类,而不是一个接口,就像父类简化的例子。

编辑:其实只有一个基类被允许,所以我把孩子键入​​回接口而不是类。

编辑:如果我删除两个“where”约束中的任何一个,则错误全部消失。是因为他们相互依赖吗?

+0

你已经标记了这两个模板和C#? C#有泛型而不是模板。你也说A的一个实例是B的多个实例的父 - 如何发生? – ROX

+0

显然,这不能工作,因为这些定义是相互递归的(并且在每次迭代时都会变得更复杂)。所以你必须删除一个约束,或者将其替换为一个不递归的约束。 – Phil1970

+0

@ Phil1970感谢您的帮助。最后,我会放弃这个设计,去寻找其他的东西。如果只能有一半没有点。我很好奇。为什么编译器不明确地告诉我有一个循环依赖而不是这些错误?是否因为他们根本没有涵盖这种错误? – Virus721

回答

0

你目前的方法存在的问题是它依赖于某种奇怪的递归。这就像说:

  • 一个孩子与父母
  • 一个人,即父母与具有父
  • 孩子一个人,那个孩子是有一个孩子父母的人其中有一位父母
  • ...这不起作用!

如果你再仔细想想,父/子关系可分为两类:

public interface IChild<P, C> where P : IParent<P, C> where C : IChild<P, C> { 
    P Parent { get; set; } 
} 

public interface IParent<P, C> where P : IParent<P, C> where C : IChild<P, C> { 
    IList<C> Children { get; } 
} 

虽然这可能工作,它看起来很复杂,因为在IParent类型paremeter P不使用,在IChild中未使用类型参数C

但我认为我们走在正确的轨道上。如果你简化了接口和通用约束添加到ParentBase,你得到的东西更容易理解:

public interface IChild<P> { 
    P Parent { get; set; } 
} 

public interface IParent<C> { 
    IList<C> Children { get; } 
} 

public class ParentBase<P, C> : IParent<C> where C : IChild<P> { 
    public IList<C> Children { get; } = new List<C>(); 
} 

有了这个设计,你有separated concerns

  • 父母的接口
  • 为接口儿童
  • 和基类实现

你的榜样工程:

public class A : ParentBase<A, B> { 
} 

public class B : ParentBase<B, C>, IChild<A> { 
    public A Parent { get; set; } 
} 

public class C : IChild<B> { 
    public B Parent { get; set; } 
} 

public class Demo { 
    public static void Test() { 
     var a = new A(); 
     var b = new B() { Parent = a }; 
     var c = new C() { Parent = b }; 
     a.Children.Add(b); 
     b.Children.Add(c); 
     System.Console.WriteLine(c.Parent.Parent == a); 
    } 
} 

还有其他一些小的细节需要注意:

  • 已发布的名单,其共同向来自实施使用接口IList<T>抽象
  • 公共领域(如childrenParentBase)是不常见的,大多数人使用只读属性来封装列表本身