В сообщении блога Кэтлин Доллард в 2008 г. она представляет интересную причину использования вложенных классов в .net. Однако она также упоминает, что FxCop не любит вложенные классы. Я предполагаю, что люди, пишущие правила FxCop, не глупы, поэтому за этой позицией должна быть причина, но я не смог ее найти.
95
Ответы:
Используйте вложенный класс, когда класс, который вы встраиваете, полезен только для включающего класса. Например, вложенные классы позволяют писать что-то вроде (упрощенно):
public class SortedMap { private class TreeNode { TreeNode left; TreeNode right; } }
Вы можете сделать полное определение своего класса в одном месте, вам не нужно перепрыгивать через какие-либо обручи PIMPL, чтобы определить, как работает ваш класс, и внешнему миру не нужно ничего видеть в вашей реализации.
Если бы класс TreeNode был внешним, вам пришлось бы либо создать все поля,
public
либо создать кучуget/set
методов для его использования. Во внешнем мире будет другой класс, загрязняющий их интеллект.источник
Из учебника Sun по Java:
Зачем использовать вложенные классы? Есть несколько веских причин для использования вложенных классов, среди них:
Логическая группировка классов. Если класс полезен только для одного другого класса, то логично встроить его в этот класс и сохранить их вместе. Вложение таких «вспомогательных классов» делает их пакет более упрощенным.
Повышенная инкапсуляция. Рассмотрим два класса верхнего уровня, A и B, где B требуется доступ к членам A, которые в противном случае были бы объявлены частными. Скрывая класс B внутри класса A, члены A могут быть объявлены частными, и B может получить к ним доступ. Кроме того, сам B можно скрыть от внешнего мира.<- Это не относится к реализации вложенных классов C #, это применимо только к Java.Более читаемый и поддерживаемый код. Вложение небольших классов в классы верхнего уровня позволяет разместить код ближе к тому месту, где он используется.
источник
Полностью ленивый и потокобезопасный шаблон singleton
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(); } }
источник: https://csharpindepth.com/Articles/Singleton
источник
Это зависит от использования. Я редко когда-либо использую публичный вложенный класс, но все время использую частные вложенные классы. Частный вложенный класс может использоваться для подобъекта, который предназначен для использования только внутри родительского объекта. Примером этого может быть класс HashTable, содержащий частный объект 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, для поддержки вложения файлов частичного класса. Это позволяет также использовать файлы классов «пространства имен» для организации вложенных файлов классов в виде папок.
Например:
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 или более параметров типа. Необходимость поддерживать синхронизацию этих параметров типа между девятью типами, которые все должны были знать параметры типа, была утомительной, особенно при добавлении нового параметра. Использование общих пространств имен делает этот код более управляемым и читаемым.
источник
Если я правильно понимаю статью Кателин, она предлагает использовать вложенный класс, чтобы иметь возможность писать SomeEntity.Collection вместо EntityCollection <SomeEntity>. На мой взгляд, это противоречивый способ сэкономить вам время на вводе текста. Я почти уверен, что в реальных коллекциях приложений будут некоторые отличия в реализации, поэтому вам все равно придется создавать отдельный класс. Я думаю, что использование имени класса для ограничения других возможностей класса - не лучшая идея. Это загрязняет интеллект и усиливает зависимости между классами. Использование пространств имен - это стандартный способ управления областью классов. Однако я считаю, что использование вложенных классов, таких как комментарий @hazzen, допустимо, если у вас нет множества вложенных классов, что является признаком плохого дизайна.
источник
Я часто использую вложенные классы, чтобы скрыть детали реализации. Пример из ответа Эрика Липперта здесь:
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) }
в то время как
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 }
источник
Другое использование вложенных классов, еще не упомянутое, - это разделение универсальных типов. Например, предположим, что кто-то хочет иметь несколько общих семейств статических классов, которые могут принимать методы с различным количеством параметров вместе со значениями для некоторых из этих параметров и генерировать делегаты с меньшим количеством параметров. Например, кто-то хочет иметь статический метод, который может принимать
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);
Использование вложенных универсальных типов позволяет определить, какие делегаты применимы к каким частям общего описания типа.
источник
Вложенные классы могут использоваться для следующих нужд:
источник
Как упоминал nawfal о реализации паттерна Абстрактная фабрика, этот код может быть расширен для достижения паттерна Кластеры классов, который основан на паттерне Абстрактная фабрика.
источник
Мне нравится вкладывать исключения, уникальные для одного класса, т.е. те, которые никогда не выбрасываются ни из какого другого места.
Например:
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); } } }
источник
Основываясь на моем понимании этой концепции, мы могли бы использовать эту функцию, когда классы концептуально связаны друг с другом. Я имею в виду, что некоторые из них представляют собой один элемент в нашем бизнесе, как сущности, существующие в мире DDD, которые помогают агрегированному корневому объекту завершить его бизнес-логику.
Чтобы пояснить, я покажу это на примере:
Представьте, что у нас есть два класса, например Order и OrderItem. В классе заказа мы собираемся управлять всеми элементами orderItem, а в OrderItem мы храним данные об одном заказе для пояснения, вы можете увидеть классы ниже:
class Order { private List<OrderItem> _orderItems = new List<OrderItem>(); public void AddOrderItem(OrderItem line) { _orderItems.Add(line); } public double OrderTotal() { double total = 0; foreach (OrderItem item in _orderItems) { total += item.TotalPrice(); } return total; } // Nested class public class OrderItem { public int ProductId { get; set; } public int Quantity { get; set; } public double Price { get; set; } public double TotalPrice() => Price * Quantity; } } class Program { static void Main(string[] args) { Order order = new Order(); Order.OrderItem orderItem1 = new Order.OrderItem(); orderItem1.ProductId = 1; orderItem1.Quantity = 5; orderItem1.Price = 1.99; order.AddOrderItem(orderItem1); Order.OrderItem orderItem2 = new Order.OrderItem(); orderItem2.ProductId = 2; orderItem2.Quantity = 12; orderItem2.Price = 0.35; order.AddOrderItem(orderItem2); Console.WriteLine(order.OrderTotal()); ReadLine(); } }
источник