Приватні внутрішні класи в C # - чому їх не використовують частіше?


76

Я відносно новачок у C #, і кожного разу, коли я починаю працювати над проектом C # (я працював лише над майже зрілими проектами в C #), мені цікаво, чому немає внутрішніх класів?

Можливо, я не розумію їх мети. Для мене внутрішні класи - принаймні приватні внутрішні класи - дуже схожі на "внутрішні процедури" в 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 буде використовуватися (принаймні на деякий час) лише ClassA, я гадаю, що цей код буде краще виразити наступним чином:

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

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

Я був би радий почути від вас на цю тему - я маю рацію?


1
Також зверніть увагу, що ви не можете мати методи розширення у вкладених класах ..
nawfal

Відповіді:


80

Вкладені класи (мабуть, краще уникати слова "внутрішній", оскільки вкладені класи в C # дещо відрізняються від внутрішніх класів у 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
        }
    }
}

Ми могли б зробити з якоюсь мовною підтримкою, щоб зробити щось із цього автоматично - і є багато варіантів, які я тут не показував, наприклад, насправді не використовувати вкладений клас для всіх значень, або використовувати той самий вкладений клас для кількох значень, але надаючи їм різні параметри конструктора. Але в основному той факт, що вкладений клас може викликати приватний конструктор, дає велику силу.


12
Дуже розумний! Але я ніколи не буду ним користуватися. Автори програмних програм та їх читачі (!) Часто є розумними людьми. Вони думають про багато яскравих речей, і це круто. Але супровідники програмного забезпечення - це звичайні люди, які не читали Gamma / Skeet. Мій гість: чим більше він розумний, тим менше підтримується ...
Сільвен Родріг

29
Це правда, поки це не стане визнаним зразком. Це як "while ((line = streamReader.ReadLine ())! = Null)" виглядає погано для початку - побічні ефекти за деякий час! Але коли це ідіоматично, ви долаєте цей запах і цінуєте лаконічність.
Джон Скіт,

2
@Jon, дякую! Думаю, я зрозумів це зараз. MyCleverEnumвиступає як власником членів переліку, так і інтерфейсом кожного члена переліку, тому він є більш стислим, ніж інші рішення. Використання вбудованих класів гарантує, що класи підтримки приховані поза межами класу. Приватний конструктор змушує клас поводитися як статичний / одиночний клас поза класом, але як абстрактний базовий клас всередині класу.
devuxer

1
Одне, що мені здавалося неповоротким із використанням такого масштабування класів, - це те, що публічні внутрішні класи в підсумку мають "пунктирні" публічні імена. Чи є чистий спосіб, щоб загальнодоступний клас мав такий самий обсяг, як вкладений клас, але був загальнодоступним за допомогою "простого" імені?
supercat

1
@supercat, Ви можете розмістити директиву using у верхній частині файлу using FirstCleverEnum = MyCleverEnum.FirstCleverEnum. Це може пом'якшити проблему, але немає можливості "експортувати" цей псевдонім.
Марті Ніл,

31

Design Guidelines Framework має кращі правила для використання вкладених класів , які я знайшов на сьогоднішній день.

Ось короткий підсумковий список:

  1. Використовуйте вкладені типи, коли взаємозв'язок між типом і вкладеним типом є такою, як бажана семантика доступності членів.

  2. Як НЕ використовувати загальнодоступні вкладені типи в якості логічної конструкції групи

  3. Уникайте використання загальнодоступних вкладених типів.

  4. Як НЕ використовувати вкладені типи , якщо тип може посилатися поза містить типу.

  5. Як НЕ використовувати вкладені типи , якщо вони повинні бути створені з допомогою клієнтського коду.

  6. Як НЕ визначати вкладений тип в якості члена інтерфейсу.


1
деякі з них досить очевидні (наприклад, 4 і 5). Але чи можете ви вказати на причини, чому рекомендуються інші правила?
Peter Bagnall,

@PeterBagnall це лише я, або 6-е суперечить прийнятій відповіді?
jungle_mole

@jungle_mole вкладений тип, що реалізує загальнодоступний інтерфейс, чудовий і загальний. Тип вкладених типів ніколи не повинен виставлятися публічно.
Ерік

@ Ерік так. я просто поміняв головою дві частини двох "конструкцій". Причини цього 6-го очевидні. про що я навіть думав
jungle_mole

12

Ви повинні обмежити обов'язки кожного класу, щоб кожен з них залишався простим, перевіряним та багаторазовим . Приватні внутрішні класи суперечать цьому. Вони сприяють складності зовнішнього класу, їх не можна перевірити і не можна повторно використовувати.


Прості, перевіряються та багаторазові дуже великі. Але як щодо інкапсуляції? Коли щось не слід бачити зовні, навіщо це публічно публікувати? У моїй книзі внутрішні заняття можуть зробити все це дещо простішим. І вони перевіряються (з релексією)!
Sylvain Rodrigue

4
Я б обрав інкапсуляцію як аргумент проти внутрішніх класів. Внутрішні класи можуть отримати доступ до приватних членів своїх зовнішніх класів!
Вім Коен

Ваше право ! Маючи внутрішній клас, який додає якусь зв'язок між ним та зовнішнім класом - я спочатку цього не бачив - вибачте. І спасибі!
Sylvain Rodrigue

9
Я бачу, що програмісти постійно використовують структури даних, такі як кортежі, замість приватних класів. Я точно стверджую, що приватний клас веде до коду, який легше читається і менш схильний до помилок. Аргументувати, що це порушує інкапсуляцію, неправильно. У приватному класі немає нічого, що порушує інкапсуляцію. Він міститься всередині класу і повинен мати і має всі права будь-якого іншого члена зовнішнього класу.
Марк

Як і у всьому, це залежить. І в цьому випадку я думаю, що це залежить від особливостей вкладеного класу. Наприклад, якщо ви створюєте dto всередині батьківського класу (наприклад, щоб підтримувати чистоту підписів методів), тоді немає чого перевіряти. Це нічим не відрізняється від створення приватних Tuples imo, просто зрозуміліше. Але якщо ви створите багатий вкладений клас, який має функціонал, то вам доведеться погано провести час.
Sinaesthetic

3

Для мене особисто я створюю приватні внутрішні класи, лише якщо мені потрібно створити в процесі колекції об'єкта, які можуть потребувати методів на них.

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


1
Але оскільки вони є приватними класами, інші розробники не повинні про них знати, якщо, звичайно, вони не повинні підтримувати зовнішній клас. Звичайно, це може бути проблемою, коли люди не знають про приватні внутрішні класи. Дякую за вашу відповідь !
Sylvain Rodrigue

16
Якби це заплутало моїх розробників, я б отримав нових розробників.
DancesWithBamboo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.