Почему/когда вы должны использовать вложенные классы в .net? Или не так ли?

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

Ответ 1

Использовать вложенный класс, когда класс, который вы вложен, полезен только для окружающего класса. Например, вложенные классы позволяют вам написать что-то вроде (упрощенного):

public class SortedMap {
    private class TreeNode {
        TreeNode left;
        TreeNode right;
    }
}

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

Если класс TreeNode был внешним, вам нужно было бы сделать все поля public или создать кучу методов get/set, чтобы использовать его. У внешнего мира был бы другой класс, загрязняющий их intellisense.

Ответ 2

Из учебника Sun Java:

Зачем использовать вложенные классы? Существует несколько веских причин для использования вложенных классов, среди которых:

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

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

Увеличение инкапсуляции. Рассмотрим два класса верхнего уровня: A и B, где B нужен доступ к членам A, которые в противном случае были бы объявлены частными. Скрывая класс B в классе A, члены A могут быть объявлены частными и B может получить к ним доступ. Кроме того, сам B можно скрывать от внешнего мира. < - Это не относится к реализации вложенных классов С#, это относится только к Java.

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

Ответ 3

Полностью ленивый и потокобезопасный одноэлементный шаблон

public sealed class Singleton
{
    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return Nested.instance;
        }
    }

    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

источник: http://www.yoda.arachsys.com/csharp/singleton.html

Ответ 4

Это зависит от использования. Я редко использовал бы общедоступный класс, но все время использовал частные вложенные классы. Частный вложенный класс может использоваться для под-объекта, который предназначен для использования только внутри родителя. Примером этого может быть, если класс HashTable содержит закрытый объект Entry для хранения данных только внутри.

Если класс предназначен для использования вызывающим пользователем (извне), мне обычно нравится сделать его отдельным автономным классом.

Ответ 5

В дополнение к другим причинам, перечисленным выше, есть еще одна причина, по которой я могу думать не только о том, чтобы использовать вложенные классы, но на самом деле публичные вложенные классы. Для тех, кто работает с несколькими родовыми классами, которые используют одни и те же параметры типового типа, возможность объявления общего пространства имен будет чрезвычайно полезна. К сожалению,.Net(или, по крайней мере, С#) не поддерживает идею общих пространств имен. Поэтому для достижения той же цели мы можем использовать общие классы для достижения одной и той же цели. Возьмем следующие примеры классов, связанных с логическим объектом:

public  class       BaseDataObject
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

public  class       BaseDataObjectList
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
:   
                    CollectionBase<tDataObject>
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

public  interface   IBaseBusiness
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

public  interface   IBaseDataAccess
                    <
                        tDataObject, 
                        tDataObjectList, 
                        tBusiness, 
                        tDataAccess
                    >
        where       tDataObject     : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
        where       tBusiness       : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
        where       tDataAccess     : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}

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

public
partial class   Entity
                <
                    tDataObject, 
                    tDataObjectList, 
                    tBusiness, 
                    tDataAccess
                >
        where   tDataObject     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
        where   tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
        where   tBusiness       : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
        where   tDataAccess     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{

    public  class       BaseDataObject {}

    public  class       BaseDataObjectList : CollectionBase<tDataObject> {}

    public  interface   IBaseBusiness {}

    public  interface   IBaseDataAccess {}

}

Затем, используя частичные классы, предложенные Эриком ван Бракелем в предыдущем комментарии, вы можете разделить классы на отдельные вложенные файлы. Я рекомендую использовать расширение Visual Studio, например NestIn, для поддержки вложения файлов частичного класса. Это позволяет также использовать файлы классов пространства имен для организации вложенных файлов классов в папке, как путь.

Например:

Entity.cs

public
partial class   Entity
                <
                    tDataObject, 
                    tDataObjectList, 
                    tBusiness, 
                    tDataAccess
                >
        where   tDataObject     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
        where   tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
        where   tBusiness       : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
        where   tDataAccess     : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{
}

Entity.BaseDataObject.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  class   BaseDataObject
    {

        public  DataTimeOffset  CreatedDateTime     { get; set; }
        public  Guid            CreatedById         { get; set; }
        public  Guid            Id                  { get; set; }
        public  DataTimeOffset  LastUpdateDateTime  { get; set; }
        public  Guid            LastUpdatedById     { get; set; }

        public
        static
        implicit    operator    tDataObjectList(DataObject dataObject)
        {
            var returnList  = new tDataObjectList();
            returnList.Add((tDataObject) this);
            return returnList;
        }

    }

}

Entity.BaseDataObjectList.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  class   BaseDataObjectList : CollectionBase<tDataObject>
    {

        public  tDataObjectList ShallowClone() 
        {
            var returnList  = new tDataObjectList();
            returnList.AddRange(this);
            return returnList;
        }

    }

}

Entity.IBaseBusiness.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  interface   IBaseBusiness
    {
        tDataObjectList Load();
        void            Delete();
        void            Save(tDataObjectList data);
    }

}

Entity.IBaseDataAccess.cs

partial class   Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{

    public  interface   IBaseDataAccess
    {
        tDataObjectList Load();
        void            Delete();
        void            Save(tDataObjectList data);
    }

}

Затем файлы в проводнике для визуальных студий будут организованы следующим образом:

Entity.cs
+   Entity.BaseDataObject.cs
+   Entity.BaseDataObjectList.cs
+   Entity.IBaseBusiness.cs
+   Entity.IBaseDataAccess.cs

И вы бы использовали общее пространство имен, как показано ниже:

User.cs

public
partial class   User
:
                Entity
                <
                    User.DataObject, 
                    User.DataObjectList, 
                    User.IBusiness, 
                    User.IDataAccess
                >
{
}

User.DataObject.cs

partial class   User
{

    public  class   DataObject : BaseDataObject 
    {
        public  string  UserName            { get; set; }
        public  byte[]  PasswordHash        { get; set; }
        public  bool    AccountIsEnabled    { get; set; }
    }

}

User.DataObjectList.cs

partial class   User
{

    public  class   DataObjectList : BaseDataObjectList {}

}

User.IBusiness.cs

partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}

User.IDataAccess.cs

partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}

И файлы будут организованы в проводнике решений следующим образом:

User.cs
+   User.DataObject.cs
+   User.DataObjectList.cs
+   User.IBusiness.cs
+   User.IDataAccess.cs

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

Ответ 6

Если я правильно понимаю статью Katheleen, она предлагает использовать вложенный класс, чтобы иметь возможность писать SomeEntity.Collection вместо EntityCollection <SomeEntity> . По-моему, это спорный способ спасти вас от типизации. Я уверен, что в реальных приложениях приложения будут иметь определенную разницу в реализациях, поэтому вам все равно придется создавать отдельный класс. Я думаю, что использование имени класса для ограничения других классов не является хорошей идеей. Он загрязняет intellisense и усиливает зависимости между классами. Использование пространств имен является стандартным способом управления областью классов. Однако я считаю, что использование вложенных классов, таких как комментарий @hazzen, приемлемо, если у вас нет тонны вложенных классов, что является признаком плохого дизайна.

Ответ 7

Я часто использую вложенные классы, чтобы скрыть детали реализации. Пример от Эрика Липперта ответьте здесь:

abstract public class BankAccount
{
    private BankAccount() { }
    // Now no one else can extend BankAccount because a derived class
    // must be able to call a constructor, but all the constructors are
    // private!
    private sealed class ChequingAccount : BankAccount { ... }
    public static BankAccount MakeChequingAccount() { return new ChequingAccount(); }
    private sealed class SavingsAccount : BankAccount { ... }
}

Этот шаблон становится еще лучше с использованием дженериков. См. этот вопрос для двух интересных примеров. Поэтому я заканчиваю писать

Equality<Person>.CreateComparer(p => p.Id);

вместо

new EqualityComparer<Person, int>(p => p.Id);

Также у меня может быть общий список Equality<Person>, но не EqualityComparer<Person, int>

var l = new List<Equality<Person>> 
        { 
         Equality<Person>.CreateComparer(p => p.Id),
         Equality<Person>.CreateComparer(p => p.Name) 
        }

где as

var l = new List<EqualityComparer<Person, ??>>> 
        { 
         new EqualityComparer<Person, int>>(p => p.Id),
         new EqualityComparer<Person, string>>(p => p.Name) 
        }

невозможно. Это преимущество вложенного класса, наследующего от родительского класса.

Другой случай (того же характера - скрытие реализации) - это когда вы хотите, чтобы члены класса (поля, свойства и т.д.) были доступны только для одного класса:

public class Outer 
{
   class Inner //private class
   {
       public int Field; //public field
   }

   static inner = new Inner { Field = -1 }; // Field is accessible here, but in no other class
}

Ответ 8

Другое использование, еще не упомянутое для вложенных классов, - это сегрегация общих типов. Например, предположим, что нужно иметь некоторые общие семейства статических классов, которые могут принимать методы с различным количеством параметров вместе со значениями для некоторых из этих параметров и генерировать делегаты с меньшим количеством параметров. Например, один хочет иметь статический метод, который может принимать Action<string, int, double> и выдать String<string, int>, который будет вызывать переданное действие 3.5, как double; также может потребоваться статический метод, который может принимать a Action<string, int, double> и выдать Action<string>, передав 7 как int и 5.3 как double. Используя общие вложенные классы, можно организовать вызовы метода как-то вроде:

MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5);
MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3);

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

MakeDelegate<string,int>.WithParams(theDelegate, 3.5);
MakeDelegate<string>.WithParams(theDelegate, 7, 5.3);

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

Ответ 9

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

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

Ответ 10

В качестве nawfal упоминается реализация абстрактного шаблона Factory, этот код может быть расширен для достижения Класс кластеров, который основан на шаблоне Abstract Factory.

Ответ 11

Мне нравится выделять исключения, которые уникальны для одного класса, т.е. которые никогда не выбрасываются из любого другого места.

Например:

public class MyClass
{
    void DoStuff()
    {
        if (!someArbitraryCondition)
        {
            // This is the only class from which OhNoException is thrown
            throw new OhNoException(
                "Oh no! Some arbitrary condition was not satisfied!");
        }
        // Do other stuff
    }

    public class OhNoException : Exception
    {
        // Constructors calling base()
    }
}

Это помогает сохранить ваши файлы проектов в порядке и не полны ста маленьких маленьких классов исключений.

Ответ 12

Имейте в виду, что вам нужно протестировать вложенный класс. Если он является конфиденциальным, вы не сможете его протестировать отдельно.

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

Таким образом, вы можете использовать только частные вложенные классы с низкой сложностью.

Ответ 13

да для этого случая:

class Join_Operator
{

    class Departamento
    {
        public int idDepto { get; set; }
        public string nombreDepto { get; set; }
    }

    class Empleado
    {
        public int idDepto { get; set; }
        public string nombreEmpleado { get; set; }
    }

    public void JoinTables()
    {
        List<Departamento> departamentos = new List<Departamento>();
        departamentos.Add(new Departamento { idDepto = 1, nombreDepto = "Arquitectura" });
        departamentos.Add(new Departamento { idDepto = 2, nombreDepto = "Programación" });

        List<Empleado> empleados = new List<Empleado>();
        empleados.Add(new Empleado { idDepto = 1, nombreEmpleado = "John Doe." });
        empleados.Add(new Empleado { idDepto = 2, nombreEmpleado = "Jim Bell" });

        var joinList = (from e in empleados
                        join d in departamentos on
                        e.idDepto equals d.idDepto
                        select new
                        {
                            nombreEmpleado = e.nombreEmpleado,
                            nombreDepto = d.nombreDepto
                        });
        foreach (var dato in joinList)
        {
            Console.WriteLine("{0} es empleado del departamento de {1}", dato.nombreEmpleado, dato.nombreDepto);
        }
    }
}