Частные внутренние классы в С# - почему они не используются чаще?

Я относительно новичок в С#, и каждый раз, когда я начинаю работать над проектом С# (я работал только над почти зрелыми проектами на С#), мне интересно, почему нет внутренних классов?

Может быть, я не понимаю их цели. Для меня внутренние классы - по крайней мере, частные внутренние классы - во многом похожи на "внутренние процедуры" в Pascal/Modula-2/Ada: они позволяют разбивать основной класс на более мелкие части, чтобы облегчить понимание.

Пример: вот что видят большую часть времени:

public class ClassA
{
   public MethodA()
   {
      <some code>
      myObjectClassB.DoSomething(); // ClassB is only used by ClassA
      <some code>
   }
}

public class ClassB
{
   public DoSomething()
   {
   }
}

Так как ClassB будет использоваться (хотя бы некоторое время) только классом A, я предполагаю, что этот код будет лучше выражен следующим образом:

   public class ClassA
   {
      public MethodA()
      {
         <some code>
         myObjectClassB.DoSomething(); // Class B is only usable by ClassA
         <some code>
      }

      private class ClassB
      {
         public DoSomething()
         {
         }
      }
   }

Я был бы рад услышать от вас по этому вопросу - я прав?

Ответ 1

Вложенные классы (возможно, лучше всего избегать слова "внутренний", поскольку вложенные классы в С# несколько отличаются от внутренних классов в Java) действительно могут быть очень полезными.

Один шаблон, который не упоминался, - это шаблон "лучшего перечисления", который может быть еще более гибким, чем тот, что в Java:

public abstract class MyCleverEnum
{
    public static readonly MyCleverEnum First = new FirstCleverEnum();
    public static readonly MyCleverEnum Second = new SecondCleverEnum();

    // Can only be called by this type *and nested types*
    private MyCleverEnum()
    {
    }

    public abstract void SomeMethod();
    public abstract void AnotherMethod();

    private class FirstCleverEnum : MyCleverEnum
    {
        public override void SomeMethod()
        {
             // First-specific behaviour here
        }

        public override void AnotherMethod()
        {
             // First-specific behaviour here
        }
    }

    private class SecondCleverEnum : MyCleverEnum
    {
        public override void SomeMethod()
        {
             // Second-specific behaviour here
        }

        public override void AnotherMethod()
        {
             // Second-specific behaviour here
        }
    }
}

Мы могли бы сделать с некоторой поддержкой языка, чтобы сделать это автоматически, - и есть много вариантов, которые я здесь не показывал, например, фактически не использовал вложенный класс для всех значений или не использовал один и тот же вложенный класс для несколько значений, но давая им разные параметры конструктора. Но в принципе, тот факт, что вложенный класс может вызвать частный конструктор, дает много мощности.

Ответ 2

Framework Design Guidelines содержит лучшие правила для использования вложенных классов, которые я нашел на сегодняшний день.

Вот краткий сводный список:

  • Использовать вложенные типы, когда отношения между типом и вложенным типом являются такой семантикой доступности элементов.

  • НЕ использовать общедоступные вложенные типы как конструкцию логической группы

  • Не используйте общедоступные вложенные типы.

  • НЕ используйте вложенные типы, если тип, вероятно, будет указан за пределами содержащего типа.

  • НЕ используйте вложенные типы, если им нужно создать экземпляр клиентским кодом.

  • НЕ задайте вложенный элемент как член интерфейса.

Ответ 3

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

Ответ 4

Для меня лично я создаю только частные внутренние классы, если мне нужно создать внутрипроцессные коллекции объекта, которые могут потребовать от них методы.

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