에서 캐슬린 Dollard의 2008 블로그 게시물 , 그녀는 그물에 중첩 된 클래스를 사용하는 흥미로운 이유를 제시한다. 그러나 그녀는 또한 FxCop이 중첩 클래스를 좋아하지 않는다고 언급합니다. 나는 FxCop 규칙을 작성하는 사람들이 어리석지 않다고 가정하고 있으므로 그 위치 뒤에 추론이 있어야하지만 찾을 수 없었습니다.
에서 캐슬린 Dollard의 2008 블로그 게시물 , 그녀는 그물에 중첩 된 클래스를 사용하는 흥미로운 이유를 제시한다. 그러나 그녀는 또한 FxCop이 중첩 클래스를 좋아하지 않는다고 언급합니다. 나는 FxCop 규칙을 작성하는 사람들이 어리석지 않다고 가정하고 있으므로 그 위치 뒤에 추론이 있어야하지만 찾을 수 없었습니다.
답변:
중첩하는 클래스가 둘러싸는 클래스에만 유용 할 때 중첩 된 클래스를 사용하십시오. 예를 들어, 중첩 클래스를 사용하면 다음과 같이 작성할 수 있습니다.
public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
}
한 곳에서 클래스를 완전히 정의 할 수 있으며, 클래스 작동 방식을 정의하기 위해 PIMPL 후프를 뛰어 넘을 필요가 없으며, 외부 세계는 구현을 볼 필요가 없습니다.
TreeNode 클래스가 외부 클래스 인 경우 모든 필드를 만들거나이 를 사용하기 public
위한 여러 get/set
메서드를 만들어야 합니다. 외부 세계는 지능을 오염시키는 또 다른 계급을 가질 것입니다.
중첩 클래스를 사용하는 이유 중첩 클래스를 사용하는 몇 가지 이유는 다음과 같습니다.
논리적 클래스 그룹화 — 클래스가 다른 하나의 클래스에만 유용하면 해당 클래스에 포함하고 두 클래스를 함께 유지하는 것이 논리적입니다. 이러한 "도우미 클래스"를 중첩하면 패키지가 더욱 간소화됩니다.
캡슐화 향상-B는 비공개로 선언 된 A의 멤버에 액세스해야하는 두 개의 최상위 클래스 A와 B를 고려합니다. 클래스 A 내에 클래스 B를 숨기면 A의 멤버를 private으로 선언하고 B가 액세스 할 수 있습니다. 또한 B 자체는 외부 세계에서 숨길 수 있습니다. <-이것은 C #의 중첩 클래스 구현에는 적용되지 않으며 Java에만 적용됩니다.
더 읽기 쉽고 관리하기 쉬운 코드 — 최상위 클래스 내에 작은 클래스를 중첩하면 코드가 사용되는 위치에 더 가깝게 배치됩니다.
완전히 지연되고 스레드로부터 안전한 싱글 톤 패턴
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();
}
}
위에 나열된 다른 이유 외에도 중첩 클래스를 사용할뿐만 아니라 실제로 공용 중첩 클래스를 사용해야하는 이유가 하나 더 있습니다. 동일한 제네릭 형식 매개 변수를 공유하는 여러 제네릭 클래스로 작업하는 사람들에게는 제네릭 네임 스페이스를 선언하는 기능이 매우 유용합니다. 불행히도 .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 {}
}
그런 다음 이전 주석에서 Erik van Brakel이 제안한 부분 클래스를 사용하여 클래스를 별도의 중첩 파일로 분리 할 수 있습니다. 부분 클래스 파일 중첩을 지원하려면 NestIn과 같은 Visual Studio 확장을 사용하는 것이 좋습니다. 이렇게하면 "네임 스페이스"클래스 파일을 사용하여 폴더에 중첩 된 클래스 파일을 구성 할 수도 있습니다.
예를 들면 :
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 개 이상의 유형 매개 변수를 포함하는 "일반 네임 스페이스"를 구축했습니다. 특히 새 매개 변수를 추가 할 때 유형 매개 변수를 알기 위해 필요한 모든 유형 매개 변수를 9 개 유형간에 동기화 된 상태로 유지해야하는 것은 지루했습니다. 일반 네임 스페이스를 사용하면 해당 코드를 훨씬 더 쉽게 관리하고 읽을 수 있습니다.
Katheleen의 기사 권한을 이해하면 EntityCollection <SomeEntity> 대신 SomeEntity.Collection을 작성할 수 있도록 중첩 클래스를 사용할 것을 제안합니다. 제 생각에는 타이핑을 절약하는 것은 논란의 여지가있는 방법입니다. 실제 응용 프로그램 컬렉션에서 구현에 약간의 차이가있을 것이라고 확신하므로 어쨌든 별도의 클래스를 만들어야합니다. 클래스 이름을 사용하여 다른 클래스 범위를 제한하는 것은 좋은 생각이 아니라고 생각합니다. 지능을 오염시키고 클래스 간의 종속성을 강화합니다. 네임 스페이스 사용은 클래스 범위를 제어하는 표준 방법입니다. 그러나 @hazzen 주석과 같은 중첩 클래스의 사용은 잘못된 디자인의 신호 인 수많은 중첩 클래스가없는 한 허용됩니다.
구현 세부 사항을 숨기기 위해 중첩 클래스를 자주 사용합니다. Eric Lippert의 답변 예 :
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>
는 AS 3.5 통과 제공된 액션을 호출 할 double
; 하나는 한는 걸릴 수 정적 메소드가하실 수 Action<string, int, double>
및 수율 Action<string>
, 전달 7
은 AS int
와 5.3
는 AS를 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 추상 팩토리 패턴의 상술 구현, 그 코드를 달성하기 위해 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);
}
}
}
이 개념에 대한 이해를 바탕으로 클래스가 개념적으로 서로 관련 될 때이 기능을 사용할 수 있습니다. 나는 그들 중 일부가 비즈니스 로직을 완성하기 위해 집계 루트 개체를 돕는 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();
}
}