Деревья наследования и защищенные конструкторы в С#

Учитывая следующее дерево наследования, какой был бы лучший способ его реализации таким образом, который работает?

abstract class Foo<T> : IEnumerable<T>
{
    public abstract Bar CreateBar();
}

class Bar<T> : Foo<T>
{
    // Bar provide a proxy interface to Foo and limit access nicely.
    // The general public shouldn't be making these though, they have access
    // via CreateBar()
    protected Bar(Foo base)
    {
        // snip...
    }
}

class Baz<T> : Foo<T>
{
    public Bar CreateBar()
    {
        return new Bar(this);
    }
}

Это не выполняется: 'Bar.Bar()' is inaccessible due to its protection level.

Я не хочу, чтобы конструктор был общедоступным, только классы, которые наследуют от Foo, должны иметь возможность создавать Bar s. Bar является специализированным Foo, и любой тип Foo должен иметь возможность его создать. Public internal является "опцией" здесь, так как большинство предопределенных расширений для Foo будет внутренним для DLL, но я считаю это неаккуратным ответом, так как любой, кто приходит позже, который хочет создать свой собственный тип Foo или Baz (что может случиться) будет зависеть от реализации по умолчанию CreateBar(), которая может или не может удовлетворить их потребности.

Возможно, есть способ рефакторинга, чтобы он работал хорошо? Я стучу головой о стену, пытаясь разработать это, чтобы он работал.

Изменить (дополнительная информация):

Чуть более конкретный: Foo реализует IEnumerable и длинную историю, Bar предоставляет тот же интерфейс, но ограниченному подмножеству этого перечислимого объекта. Все Foo должны иметь возможность создавать подмножества самих себя (например, Bar) и возвращать их. Но я не хочу, чтобы все, кто когда-либо хотел реализовать Foo, должны были беспокоиться об этом, потому что Bar будет делать прокси и беспокоиться о ограничении диапазона и т.д.

Ответ 1

Хорошо, новый ответ:

  • Разделить панель на интерфейс и конкретный класс.
  • Выразить публичный абстрактный метод с точки зрения IBar.
  • Сделайте Bar приватным вложенным классом в Foo, реализуя IBar. Дайте ему внутренний конструктор, который вы можете вызвать из Foo.
  • Напишите защищенный метод в Foo, который создает экземпляр Bar из самого себя. Классы, полученные из Foo, могут использовать это для реализации абстрактного метода, если достаточно проксирования достаточно, а классы с более сложными потребностями могут просто реализовать IBar напрямую. Вы даже можете изменить абстрактный метод на виртуальный, и создать по умолчанию новый Bar от "this".

EDIT: Один из вариантов заключается в том, чтобы сделать Bar защищенным вложенным классом внутри Foo с помощью публичного конструктора. Таким образом, любой производный класс мог бы создать экземпляр для себя, но ни один несвязанный класс не смог бы "увидеть" его вообще. Вам все равно нужно отделить интерфейс от реализации (так что интерфейс может быть общедоступным), но я думаю, что это хорошо.

Ответ 2

Можно ли сделать Baz вложенным типом внутри Bar? Это единственный способ предоставить вам больше доступа к Bar, чем в противном случае. Просто наличие одного и того же родительского класса предоставляет только доступ к защищенным членам Foo, а Foo не имеет специального доступа к Bar. Я подозреваю, что есть другие извилистые способы сделать это с вложенными типами, но на самом деле это будет очень неприятно для инженеров-технологов.

Однако это довольно странный дизайн, чтобы заставить один производный класс создать экземпляр другого класса, производного от того же базового класса. Это действительно то, что вам нужно? Возможно, если бы вы выразили это более конкретно, было бы легче придумать альтернативные проекты.

Ответ 3

Вы можете получить доступ к конструктору Bar через вложенный тип внутри Foo:

abstract class Foo<T> : IEnumerable<T>
{
  public abstract Bar<T> CreateBar();

  protected Bar<T> CreateBar(Foo<T> f) { return new FooBar(f); }

  private class FooBar : Bar<T> 
   { public FooBar(Foo<T> f) : base(f) {}   
   }
}

class Bar<T> : Foo<T>
{ protected Bar(Foo<T> @base) {}
}

class Baz<T> : Foo<T>
{
    public override Bar<T> CreateBar() 
    {
        return CreateBar(this);
    }
}

Ответ 4

Забудьте на мгновение, что Bar происходит от Foo. Если вы это сделаете, похоже, что проблема заключается в следующем: "Как сделать так, чтобы каждый подкласс Foo мог создавать панель, даже если подкласс не имеет доступа к конструктору Bar?"

Это довольно простая проблема:

public class Foo
{
   protected static Bar CreateBarInstance()
   {
      return new Bar();
   }
   public virtual Bar CreateBar()
   {
      return CreateBarInstance();
   }
}

public class Bar
{
    internal Bar()
    {
    }
}

public class Baz : Foo
{
    public override Bar CreateBar()
    {
        Bar b = base.CreateBar();
        // manipulate the Bar in some fashion
        return b;
    }
}

Если вы хотите гарантировать, что подклассы Foo не имеют доступа к конструктору Bar, поместите их в другую сборку.

Теперь, чтобы получить Bar из Foo, это простое изменение:

public class Bar : Foo
{
    internal Bar()
    {
    }
    public override Bar CreateBar()
    {
        throw new InvalidOperationException("I'm sorry, Dave, I can't do that.");
    }

}

"Я не хочу, чтобы конструктор был общедоступным". Проверьте.

"Только классы, наследующие Foo, должны иметь возможность создавать Bars". Проверьте.

"Любой тип Foo должен иметь возможность создать его". Проверьте,

"Любой, кто приходит позже, который хочет создать свой собственный тип Foo или Baz (который, вероятно, произойдет), будет зависеть от реализации по умолчанию CreateBar(), которая может или не может удовлетворить их потребности". Это очень сильно зависит от того, что должно произойти в методе Foo.CreateBar(), я думаю.

Ответ 5

С# не предоставляет прямой эквивалент ключевого слова для друзей С++. Похоже, ваш дизайн требует такой конструкции.

В С++ вы можете указать, что определенный класс имеет доступ к частным/защищенным членам другого класса с помощью "друга". Примечание. Это не то же самое, что и внутренний С#, модификатор, который предоставляет доступ ко всем классам внутри одной и той же сборки.

Глядя на ваш дизайн, кажется, вы пытаетесь сделать что-то по линиям, которые потребуют друга стиля С++. Джон Скит прав, обычный дизайн в С#, чтобы компенсировать это, - это использовать вложенный класс.

Этот сообщение форума объясняет далее и показывает некоторые примеры того, как это сделать.