Смущает что-то похожее на необычно рекурсивный шаблон шаблона

У меня есть приложение, состоящее из нескольких «уровней» элементов. Некоторые элементы являются родительскими для 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: упростил пример, сделав дочерний интерфейс базовым классом, а не интерфейсом, как и родительский класс.

EDIT: на самом деле разрешен только один базовый класс, поэтому я снова превратил дочерний тип в интерфейс, а не в класс.

ИЗМЕНИТЬ: если я удалю одно из двух ограничений «где», все ошибки исчезнут. Потому что они зависят друг от друга?


person Virus721    schedule 25.07.2017    source источник
comment
Вы отметили этот шаблон и C #? В C # есть универсальные шаблоны, а не шаблоны. Также вы говорите, что экземпляр A является родительским для нескольких экземпляров B - как это может случиться?   -  person ROX    schedule 25.07.2017
comment
Очевидно, это не может работать, потому что определения взаимно рекурсивны (и усложняются на каждой итерации). Таким образом, вы должны удалить одно ограничение или заменить его нерекурсивным.   -  person Phil1970    schedule 25.07.2017
comment
@ Phil1970 Спасибо за вашу помощь. В конце концов, я просто откажусь от этого дизайна и займусь чем-нибудь другим. Если может быть только половина, в этом нет смысла. Хотя мне любопытно. Почему компилятор явно не сообщает мне, что существует циклическая зависимость вместо этих ошибок? Это потому, что они просто не охватили этот случай ошибки?   -  person Virus721    schedule 26.07.2017
comment
@ROX Да, это то, что я имел в виду, дженерики. Я исхожу из опыта работы на C ++. В любом случае это имеет значение? A может быть родительским для нескольких экземпляров B, потому что B передается как параметр шаблона Child в ParentBase ‹›, указывающий тип дочерних элементов.   -  person Virus721    schedule 26.07.2017
comment
Да, это имеет значение для универсальных шаблонов и шаблонов, поскольку они работают по-разному, и C ++ / CLI поддерживает их оба. Так что вы должны понимать их различия, если вам нужно писать оба вида.   -  person Phil1970    schedule 26.07.2017


Ответы (1)


Проблема с вашим текущим подходом в том, что он основан на какой-то странной рекурсии. Это как сказать:

  • ребенок - это человек с родителем
  • и этот родитель - человек с ребенком, у которого есть родитель
  • и этот ребенок - человек с родителем, у которого есть ребенок, у которого есть родитель
  • ... это не работает!

Если подумать еще раз, отношения родитель / потомок включают два типа:

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 параметр типа 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>();
}

С этим дизайном у вас есть отдельные проблемы:

  • интерфейс для родителей
  • интерфейс для детей
  • и реализация базового класса

Ваш пример работает:

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>, чтобы абстрагироваться от реализации
  • общедоступные поля (например, children в ParentBase) встречаются редко, большинство людей используют свойство только для чтения, чтобы инкапсулировать сам список
person ventiseis    schedule 09.08.2017