Почему / когда вы должны использовать вложенные классы в .net? Или ты не должен? - PullRequest
89 голосов
/ 08 сентября 2008

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

Ответы [ 13 ]

92 голосов
/ 08 сентября 2008

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

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

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

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

15 голосов
/ 08 сентября 2008

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

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

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

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

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

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

9 голосов
/ 05 июля 2009

Полностью ленивый и поточно-ориентированный синглтон

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

5 голосов
/ 01 апреля 2015

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

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);
    }

}

Файлы в обозревателе решений Visual Studio будут организованы следующим образом:

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 или более параметров типа. Необходимость сохранять эти параметры типов синхронизированными между девятью типами, которые все должны были знать параметры типа, была утомительной, особенно при добавлении нового параметра. Использование общих пространств имен делает этот код намного более управляемым и читаемым.

5 голосов
/ 08 сентября 2008

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

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

3 голосов
/ 08 сентября 2008

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

1 голос
/ 27 апреля 2014

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

  1. Классификация данных
  2. Когда логика основного класса сложна и вам кажется, что для управления классом требуются подчиненные объекты
  3. Когда вы что состояние и существование класса полностью зависит от включающего класса
1 голос
/ 02 сентября 2013

Другое использование, еще не упомянутое для вложенных классов, - это разделение универсальных типов. Например, предположим, что нужно иметь несколько общих семейств статических классов, которые могут принимать методы с различным числом параметров, а также значения для некоторых из этих параметров и генерировать делегаты с меньшим количеством параметров. Например, желательно иметь статический метод, который может принять Action<string, int, double> и получить String<string, int>, который будет вызывать предоставленное действие, передающее 3.5 как double; Можно также пожелать иметь статический метод, который может принимать 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);

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

0 голосов
/ 23 июня 2018

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

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);
        }
    }
}
0 голосов
/ 10 марта 2015

Помните, что вам нужно проверить вложенный класс. Если он закрыт, вы не сможете проверить его изолированно.

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

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

...