Почему / когда следует использовать вложенные классы in.net-или не стоит?
на недавнее сообщение в блоге Кэтлин Доллард, Она представляет интересную причину для использования вложенных классов в .net.однако она также упоминает, что FxCop не любит вложенные классы. Я предполагаю, что люди, пишущие правила FxCop, не глупы, поэтому за этой позицией должны быть рассуждения, но я не смог ее найти.
13 ответов:
используйте вложенный класс, когда вложенный класс полезен только для вложенного класса. Например, вложенные классы позволяют писать что-то вроде (упрощенно):
public class SortedMap { private class TreeNode { TreeNode left; TreeNode right; } }вы можете сделать полное определение вашего класса в одном месте, вам не нужно прыгать через какие-либо обручи PIMPL, чтобы определить, как работает ваш класс, и внешний мир не должен видеть ничего из вашей реализации.
Если класс TreeNode был внешним, вам либо придется сделайте все поля
publicили сделать кучуget/setметоды его использования. У внешнего мира был бы другой класс, загрязняющий их интеллект.
Зачем Использовать Вложенные Классы? Существует несколько веских причин для использования вложенных классов, среди них:
- это способ логической группировки классов, которые используются только в одном месте.
- это увеличивает инкапсуляцию.
- вложенные классы могут привести к более читаемого и поддерживаемого кода.
логическая группировка классов-если класс полезен только для одного другого класса, то логично встроить его в этот класс и держать их вместе. Вложение таких "вспомогательных классов" делает их пакет более упорядоченным.
повышенная инкапсуляция-рассмотрим два класса верхнего уровня, 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(); } }
Это зависит от использования. Я редко когда-либо использовал бы публичный вложенный класс, но все время использую частные вложенные классы. Частный вложенный класс может использоваться для подобъекта, который предназначен для использования только внутри родительского объекта. Примером этого может быть, если класс 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>и выход aString<string, int>который вызовет поставляемое действие, проходящее 3.5 какdouble; один может также хотите иметь статический метод, который может принимать anAction<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);использование вложенных универсальных типов позволяет определить, какие делегаты применимы какие части общего описания типа.
вложенные классы могут быть использованы для следующих нужд:
- классификация данных
- когда логика основного класса сложна, и вы чувствуете, что вам нужны подчиненные объекты для управления классом
- когда вы, что состояние и существование класса полностью зависит от заключающего класса
Я часто использую вложенные классы, чтобы скрыть детали реализации. пример из ответа Эрика Липперта здесь:
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