[.net] .net에서 중첩 클래스를 사용해야하는 이유 / 언제? 아니면 안 그래?

에서 캐슬린 Dollard의 2008 블로그 게시물 , 그녀는 그물에 중첩 된 클래스를 사용하는 흥미로운 이유를 제시한다. 그러나 그녀는 또한 FxCop이 중첩 클래스를 좋아하지 않는다고 언급합니다. 나는 FxCop 규칙을 작성하는 사람들이 어리석지 않다고 가정하고 있으므로 그 위치 뒤에 추론이 있어야하지만 찾을 수 없었습니다.



답변

중첩하는 클래스가 둘러싸는 클래스에만 유용 할 때 중첩 된 클래스를 사용하십시오. 예를 들어, 중첩 클래스를 사용하면 다음과 같이 작성할 수 있습니다.

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

한 곳에서 클래스를 완전히 정의 할 수 있으며, 클래스 작동 방식을 정의하기 위해 PIMPL 후프를 뛰어 넘을 필요가 없으며, 외부 세계는 구현을 볼 필요가 없습니다.

TreeNode 클래스가 외부 클래스 인 경우 모든 필드를 만들거나이 를 사용하기 public위한 여러 get/set메서드를 만들어야 합니다. 외부 세계는 지능을 오염시키는 또 다른 계급을 가질 것입니다.


답변

Sun의 Java Tutorial에서 :

중첩 클래스를 사용하는 이유 중첩 클래스를 사용하는 몇 가지 이유는 다음과 같습니다.

  • 한 곳에서만 사용되는 클래스를 논리적으로 그룹화하는 방법입니다.
  • 캡슐화를 증가시킵니다.
  • 중첩 된 클래스는 더 읽기 쉽고 유지 관리 가능한 코드로 이어질 수 있습니다.

논리적 클래스 그룹화 — 클래스가 다른 하나의 클래스에만 유용하면 해당 클래스에 포함하고 두 클래스를 함께 유지하는 것이 논리적입니다. 이러한 “도우미 클래스”를 중첩하면 패키지가 더욱 간소화됩니다.

캡슐화 향상-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();
    }
}

출처 : http://www.yoda.arachsys.com/csharp/singleton.html


답변

사용법에 따라 다릅니다. 나는 Public 중첩 클래스를 거의 사용하지 않지만 항상 Private 중첩 클래스를 사용합니다. 전용 중첩 클래스는 부모 내부에서만 사용하도록 의도 된 하위 개체에 사용할 수 있습니다. 이에 대한 예는 HashTable 클래스에 내부적으로 만 데이터를 저장하는 개인 항목 개체가 포함 된 경우입니다.

클래스가 호출자 (외부 적으로)에 의해 사용되도록 의도 된 경우 일반적으로 별도의 독립형 클래스로 만드는 것을 좋아합니다.


답변

위에 나열된 다른 이유 외에도 중첩 클래스를 사용할뿐만 아니라 실제로 공용 중첩 클래스를 사용해야하는 이유가 하나 더 있습니다. 동일한 제네릭 형식 매개 변수를 공유하는 여러 제네릭 클래스로 작업하는 사람들에게는 제네릭 네임 스페이스를 선언하는 기능이 매우 유용합니다. 불행히도 .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
}