なぜ/いつ.netでネストされたクラスを使用する必要がありますか?またはあなたはすべきではないのですか?


93

キャスリーンDollardの2008年のブログ記事、彼女は.NETで、ネストされたクラスを使用するには、興味深い理由を提示しています。ただし、FxCopはネストされたクラスを好まないことにも言及しています。私はFxCopルールを書いている人は愚かではないと想定しているので、その立場の背後には理由があるに違いありませんが、私はそれを見つけることができませんでした。


ブログ記事へのウェイバックアーカイブリンク:web.archive.org/web/20141127115939/https://blogs.msmvps.com/...は
iokevins

アウトnawfalポイント私たちの仲間、エリックリッペルトはここにこの質問の重複に答え、問題の答えが始まる、「ネストされたクラスが利用することができ、特にとき、あなたはクラス無意味外であるヘルパークラスを必要なときに使用すると、クラスを入れ子になりました外部クラスのプライベート実装の詳細。¶ネストされたクラスが役に立たないというあなたの引数は、プライベートメソッドが役に立たないという引数でもあります... "
ruffin

回答:


96

ネストしているクラスが、それを囲んでいるクラスだけに役立つ場合は、ネストしたクラスを使用します。たとえば、ネストされたクラスを使用すると、(簡略化された)次のように記述できます。

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

クラスの完全な定義を1か所で行うことができ、クラスがどのように機能するかを定義するためにPIMPLフープにジャンプする必要はありません。また、外部の世界で実装の内容を確認する必要はありません。

TreeNodeクラスが外部の場合、すべてのフィールドを作成するか、それを使用publicするために一連のget/setメソッドを作成する必要があります。外の世界は彼らのインテリセンスを汚染する別のクラスを持つでしょう。


44
これに追加するには:別のファイルで部分クラスを使用して、コードを少しよく管理することもできます。内部クラスを別のファイル(この場合はSortedMap.TreeNode.cs)に配置します。これにより、コードがクリーンに保たれると同時に、コードが分離されます:)
Erik van Brakel

1
入れ子になったクラスをパブリックAPIまたはコンテナークラスのパブリックプロパティの戻り値の型で使用している場合は、それをパブリックまたは内部にする必要がある場合があります。それが良い習慣かどうかはわかりませんが。このような場合は、入れ子になったクラスをコンテナクラスの外に引き出す方が理にかなっている可能性があります。.NetフレームワークのSystem.Windows.Forms.ListViewItem.ListViewSubItemクラスは、そのような例の1つです。
RBT 2016

16

SunのJavaチュートリアルから:

ネストされたクラスを使用する理由 ネストされたクラスを使用する説得力のある理由がいくつかあります。

  • 1つの場所でのみ使用されるクラスを論理的にグループ化する方法です。
  • カプセル化が向上します。
  • ネストされたクラスを使用すると、コードが読みやすく、保守しやすくなります。

クラスの論理的なグループ化-クラスが他の1つのクラスだけに役立つ場合、そのクラスに埋め込み、2つをまとめることが論理的です。このような「ヘルパークラス」をネストすると、パッケージがより合理化されます。

カプセル化の増加-2つの最上位クラスAとBを検討します。Bは、そうでなければプライベートとして宣言されるAのメンバーにアクセスする必要があります。クラスA内でクラスBを非表示にすることにより、Aのメンバーをプライベートに宣言し、Bがそれらにアクセスできるようにすることができます。さらに、B自体を外部の世界から隠すことができます。<-これはネストされたクラスのC#の実装には適用されません。これはJavaにのみ適用されます。

より読みやすく、保守しやすいコード-最上位クラス内に小さなクラスをネストすると、コードが使用される場所にコードが近くなります。


1
Javaの場合のように、C#で囲んでいるクラスからインスタンス変数にアクセスできないため、これは実際には当てはまりません。静的メンバーのみがアクセス可能です。
ベン・バロン

5
ただし、囲んでいるクラスのインスタンスをネストされたクラスに渡すと、ネストされたクラスはそのインスタンス変数を介してすべてのメンバーにフルアクセスできます。明示的にしてください。
Alex

@Alexいいえ、そうではありません。Javaでは、インスタンス化されたときにネストされたクラスが実際に親クラスのインスタンスをキャプチャします。これはとりわけ、親がガベージコレクションされるのを防ぐことを意味します。また、ネストされたクラスは親クラスなしではインスタンス化できないことも意味します。いいえ、これらはまったく同じではありません。
トマーシュZato -復活モニカ

2
@TomášZato私の説明は本当にかなり適切です。Javaの入れ子になったクラスには、事実上暗黙的な親インスタンス変数がありますが、C#では、内部クラスにインスタンスを明示的に渡す必要があります。この結果、あなたが言うように、Javaの内部クラスには親インスタンスが必要ですが、C#にはありません。いずれにせよ、私の主なポイントは、C#の内部クラスもその親のプライベートフィールドとプロパティにアクセスできることですが、そのためには親インスタンスを明示的に渡す必要があるということです。
Alex

9

完全にレイジーでスレッドセーフなシングルトンパターン

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


5

使い方次第です。Publicネストクラスを使用することはほとんどありませんが、常にPrivateネストクラスを使用します。ネストされたプライベートクラスは、親の内部でのみ使用することを目的としたサブオブジェクトに使用できます。この例としては、HashTableクラスに、内部的にのみデータを格納するためのプライベートエントリオブジェクトが含まれている場合があります。

クラスが呼び出し元によって(外部で)使用されることを意図している場合、私は通常、それを別個のスタンドアロンクラスにすることが好きです。


5

上記の他の理由に加えて、ネストされたクラスを使用するだけでなく、実際にはパブリックなネストされたクラスを使用することを考えることができるもう1つの理由があります。同じジェネリック型パラメーターを共有する複数のジェネリッククラスを扱う人にとって、ジェネリック名前空間を宣言する機能は非常に役立ちます。残念ながら、.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 Solution Explorerのファイルは、次のように編成されます。

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つの型間でこれらの型パラメーターの同期を維持する必要があり、面倒でした。一般的な名前空間を使用すると、そのコードははるかに管理しやすく、読みやすくなります。


3

Katheleenの記事を正しく理解していれば、彼女はEntityCollection <SomeEntity>ではなく、SomeEntity.Collectionを記述できるようにネストされたクラスを使用することを提案しています。私の意見では、タイピングの手間を省くには物議を醸す方法です。実際のアプリケーションコレクションでは実装に多少の違いがあると確信しているため、とにかく個別のクラスを作成する必要があります。クラス名を使用して他のクラスのスコープを制限することは良い考えではないと思います。インテリセンスを汚染し、クラス間の依存関係を強化します。名前空間の使用は、クラスのスコープを制御する標準的な方法です。ただし、@ hazzenコメントのようなネストされたクラスの使用は、デザインが悪い兆候であるネストされたクラスが大量にない限り、許容できることがわかります。


1

ネストされたクラスを使用して、実装の詳細を隠すことがよくあります。エリック・リペルトの答えの例はこちら:

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

このパターンはジェネリックを使用するとさらに良くなります。2つのすばらしい例については、この質問を参照してください。だから私は書くことになります

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

不可能である。これが、親クラスから継承されたネストされたクラスの利点です。

もう1つのケース(同じ性質-実装の非表示)は、クラスのメンバー(フィールド、プロパティなど)を1つのクラスだけにアクセスできるようにする場合です。

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
}

1

ネストされたクラスでまだ言及されていない別の用途は、ジェネリック型の分離です。たとえば、さまざまな数のパラメーターを持つメソッドとそれらのいくつかのパラメーターの値を受け取り、より少ないパラメーターでデリゲートを生成できる静的クラスのいくつかのジェネリックファミリーが必要であるとします。たとえば、3.5をとして渡し、指定されたアクションを呼び出すAction<string, int, double>a String<string, int>を取得して生成できる静的メソッドが必要doubleです。an Action<string, int, double>を取得してを生成し、and としてAction<string>渡す静的メソッドが必要な場合もあります。ジェネリックなネストされたクラスを使用して、メソッドの呼び出しを次のようにすることができます。7int5.3double

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

ネストされたクラスは、次のニーズに使用できます。

  1. データの分類
  2. メインクラスのロジックが複雑で、クラスを管理するために下位オブジェクトが必要であると感じる場合
  3. クラスの状態と存在が、それを含むクラスに完全に依存している場合


0

つまり、単一のクラスに固有の例外をネストするのが好きです。他の場所から決して投げられないもの。

例えば:

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

これは、プロジェクトファイルを整然と保ち、100のずんぐりした小さな例外クラスでいっぱいにならないようにするのに役立ちます。


0

ネストされたクラスをテストする必要があることを覚えておいてください。非公開の場合、単独でテストすることはできません。

ただし、属性と組み合わせて内部にすることもできますInternalsVisibleTo。ただし、これはプライベートフィールドをテスト目的でのみ内部的にするのと同じです。

したがって、複雑度の低いプライベートネストクラスのみを実装したい場合があります。


0

この場合はい:

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

どうして?ソリューションのコードにコンテキストを追加して、回答の背後にある理由を将来の読者が理解できるようにします。
Grant Miller

0

この概念についての私の理解に基づいて、クラスが概念的に相互に関連している場合にこの機能を使用できます。それらのいくつかは、ビジネスロジックを完了するために集約ルートオブジェクトを支援するDDDの世界に存在するエンティティのように、私たちのビジネスで完全な1つのアイテムです。

明確にするために、例としてこれを示します。

OrderとOrderItemのような2つのクラスがあるとします。orderクラスでは、すべての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();
        }


    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.