2017-07-25 72 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)是不常見的,大多數人使用只讀屬性來封裝列表本身