Зачем мне нужно использовать вложенные классы C#

Я пытаюсь понять о вложенных классах в C#. Я понимаю, что вложенный класс - это класс, который определен в другом классе, но я не понимаю, зачем мне это нужно.

8 ответов

Шаблон, который мне особенно нравится, - это объединение вложенных классов с шаблоном фабрики:

public abstract class BankAccount
{
  private BankAccount() {} // prevent third-party subclassing.
  private sealed class SavingsAccount : BankAccount { ... }
  private sealed class ChequingAccount : BankAccount { ... }
  public static BankAccount MakeSavingAccount() { ... }
  public static BankAccount MakeChequingAccount() { ... }
}

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

Цель обычно состоит в том, чтобы просто ограничить область действия вложенного класса. Вложенные классы по сравнению с обычными классами имеют дополнительную возможность private модификатор (а также protected конечно).

По сути, если вам нужно использовать этот класс только из "родительского" класса (с точки зрения области видимости), то обычно целесообразно определить его как вложенный класс. Если этот класс, возможно, потребуется использовать без сборки / библиотеки, то пользователю обычно удобнее определить его как отдельный (родственный) класс, независимо от того, существует ли какая-либо концептуальная связь между этими двумя классами. Хотя технически возможно создать public класс, вложенный в public родительский класс, это, на мой взгляд, редко подходящая вещь для реализации.

Вложенный класс может иметь private, protected а также protected internal модификаторы доступа вместе с public а также internal,

Например, вы реализуете GetEnumerator() метод, который возвращает IEnumerator<T> объект. Потребители не будут заботиться о фактическом типе объекта. Все, что они знают об этом, это то, что он реализует этот интерфейс. Класс, который вы хотите вернуть, не имеет прямого использования. Вы можете объявить этот класс как private вложенный класс и возвращает его экземпляр (именно так в компиляторе C# реализуются итераторы):

class MyUselessList : IEnumerable<int> {
    // ...
    private List<int> internalList;
    private class UselessListEnumerator : IEnumerator<int> {
        private MyUselessList obj;
        public UselessListEnumerator(MyUselessList o) {
           obj = o;
        }
        private int currentIndex = -1;
        public int Current {
           get { return obj.internalList[currentIndex]; }
        }
        public bool MoveNext() { 
           return ++currentIndex < obj.internalList.Count;
        }
    }
    public IEnumerator<int> GetEnumerator() {
        return new UselessListEnumerator(this);
    }
}

то, что я не понимаю, это то, почему мне нужно это делать

Я думаю, тебе никогда не нужно этого делать. Учитывая вложенный класс, как это...

class A
{
  //B is used to help implement A
  class B
  {
    ...etc...
  }
  ...etc...
}

... вы всегда можете переместить внутренний / вложенный класс в глобальную область, например так...

class A
{
  ...etc...
}

//B is used to help implement A
class B
{
  ...etc...
}

Однако, когда B используется только для реализации A, то превращение B во внутренний / вложенный класс имеет два преимущества:

  • Это не загрязняет глобальную область видимости (например, клиентский код, который может видеть A, не знает, что класс B даже существует)
  • Методы B неявно имеют доступ к закрытым членам A; тогда как, если бы B не был вложен в A, B не смог бы получить доступ к членам A, если эти члены не были внутренними или публичными; но затем, сделав этих членов внутренними или публичными, они будут доступны и другим классам (не только B); поэтому вместо этого оставьте эти методы A закрытыми и предоставьте B доступ к ним, объявив B как вложенный класс. Если вы знаете C++, это все равно, что сказать, что в C# все вложенные классы автоматически становятся "друзьями" класса, в котором они содержатся (и что объявление класса как вложенного - единственный способ объявить дружбу в C#, поскольку C# не имеет friend ключевое слово).

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

class A
{
  //used to help implement A
  class B
  {
    A m_a;
    internal B(A a) { m_a = a; }
    ...methods of B can access private members of the m_a instance...
  }
  ...etc...
}

... и построен из метода A с использованием кода, подобного этому...

//create an instance of B, whose implementation can access members of self
B b = new B(this);

Вы можете увидеть пример в ответе Мехрдада.

Существует также хорошее использование открытых вложенных членов...

Вложенные классы имеют доступ к закрытым членам внешнего класса. Таким образом, сценарий, где это правильный путь, будет при создании Comparer (т. Е. Реализация интерфейса IComparer).

В этом примере FirstNameComparer имеет доступ к закрытому члену _firstName, чего не было бы, если бы этот класс был отдельным классом...

public class Person
{
    private string _firstName;
    private string _lastName;
    private DateTime _birthday;

    //...

    public class FirstNameComparer : IComparer<Person>
    {
        public int Compare(Person x, Person y)
        {
            return x._firstName.CompareTo(y._lastName);
        }
    }

}

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

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

Вложенные классы очень полезны для реализации внутренних деталей, которые не должны быть представлены. Если вы используете Reflector для проверки таких классов, как Dictionary или Hashtable, вы найдете несколько примеров.

Может быть, это хороший пример того, когда использовать вложенные классы?

// ORIGINAL
class ImageCacheSettings { }
class ImageCacheEntry { }
class ImageCache
{
    ImageCacheSettings mSettings;
    List<ImageCacheEntry> mEntries;
}

А также:

// REFACTORED
class ImageCache
{
    Settings mSettings;
    List<Entry> mEntries;

    class Settings {}
    class Entry {}
}

PS: я не учел, какие модификаторы доступа должны применяться (частный, защищенный, публичный, внутренний)

Другие вопросы по тегам