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



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

790   13  

13 ответов:

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

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

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

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

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

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

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

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

повышенная инкапсуляция-рассмотрим два класса верхнего уровня, A и B, где B нуждается в доступе к членам A, которые в противном случае были бы объявлены частными. Скрывая класс B в классе A, члены A могут быть объявлены частными, и B может получить к ним доступ. Кроме того, сам Б может быть скрыт от внешнего мира.

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

полностью ленивый и потокобезопасный синглтон шаблон

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

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

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

в дополнение к другим причинам, перечисленным выше, есть еще одна причина, по которой я могу думать не только об использовании вложенных классов, но и о публичных вложенных классах. Для тех, кто работает с несколькими универсальными классами, которые используют одни и те же параметры универсального типа, возможность объявить универсальное пространство имен будет чрезвычайно полезной. К сожалению, .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, для поддержки вложенности частичного файл класса. Это позволяет также использовать файлы классов "пространства имен" для организации вложенных файлов классов в папке, например.

например:

сущности.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
{
}

сущности.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;
        }

    }

}

сущности.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;
        }

    }

}

сущности.Ибасебизнес.cs

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

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

}

сущности.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

и вы бы реализовали общее пространство имен следующим образом:

пользователей.cs

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

пользователей.Объект dataObject.cs

partial class   User
{

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

}

пользователей.DataObjectList.cs

partial class   User
{

    public  class   DataObjectList : BaseDataObjectList {}

}

пользователей.IBusiness.cs

partial class   User
{

    public  interface   IBusiness : IBaseBusiness {}

}

пользователей.IDataAccess.cs

partial class   User
{

    public  interface   IDataAccess : IBaseDataAccess {}

}

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

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

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

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

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

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

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

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

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

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 { ... }
}

эта картина становится еще лучше с использованием дженериков. посмотреть этот вопрос для двух интересных примеров. Так что я заканчиваю писать!--11-->

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

где, как

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
}

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

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

например:

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

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

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

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

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

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

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

Comments

    Ничего не найдено.