2009-04-29 3 views
26

C#の内部クラスの使用と構造に関するベストプラクティスは何ですか?C#の内部クラスの使用

たとえば、非常に大きな基本クラスと2つの大きな内部クラスを分割して別々の(部分クラス)コードファイルに分割するか、非常に大きな扱いにくいコードファイルにする必要がありますか?

また、パブリック継承された内部クラスを持つ抽象クラスを持つことは悪い習慣ですか?

答えて

23

典型的には私は2つの目的のいずれかの内部クラスリザーブ:親クラスは、1つ以上の抽象メソッドを持つ抽象ベースの実装であり、各サブクラスは親クラスから派生

  1. パブリッククラスを特定の実装を提供する実装です。フレームワークの設計とガイドラインを読んだ後、私は、しかし私は、列挙型に似たシナリオで、それを使用し、これは「避ける」としてマークされていることを確認 - それはおそらく同様に

  2. を悪い印象を与えているalthogh内部クラスがプライベートであり、ビジネスロジックの単位であるか、または他のクラスによって消費または使用されたときに根本的に壊れるような方法で、親クラスに緊密に結合されています。多くの場合、少し少なめに優しい「メイン」クラスよりも名前を持つ - 私は彼らの消費者/論理的な親と同じ名前空間と同じアクセスレベルでそれらを維持しようとする他のすべてのケースについては

大規模なプロジェクトでは、強く結合されたコンポーネントを最初に作成するのは、それが論理的に見えるようにしているからだと思います。それを視界から隠すと、他のコンポーネントがそれを消費できるようにクラスを公開することに害はほとんどありません。

サブクラスについて説明していますが、それは多かれ少なかれよく設計され、疎結合されたコンポーネントでなければならないことに注意してください。それらがプライベートであり、クラス間の最小の "表面領域"を保って外界に見えない場合でも、将来の拡張や変更のためにコードの保守性を大幅に緩和します。

+2

これは、「他のどのクラスでも消費されたり使用された場合、根本的に壊れています。 – Alonzzo2

9

一般に、内部クラスはプライベートであり、それらを含むクラスによってのみ使用できます。彼らが内部クラスが非常に大きい場合、彼らは彼ら自身のクラスでなければならないことを示唆します。

通常、大きな内部クラスがあるときは内部クラスが密接にクラスに含まれており、その内部メソッドにアクセスする必要があるためです。

8

これはむしろ主観的だと思いますが、「ホスト」クラスを部分的にすることで、別々のコードファイルに分割する可能性があります。

このようにすると、editing the project fileでさらに概要を知ることができ、ファイルグループはWindowsフォームのデザイナークラスと似ています。私はあなたのためにこれを自動的に行うVisual Studioアドインを見たと思いますが、どこで覚えていません。

EDIT:
私は、Visual Studioのアドインこの呼ばVSCommandsに

+2

あなたは彼らにMyClassの名前を付ける場合、彼らは、Visual Studioで自動的にこれをしないでください。 cs、MyClass.subportion.cs、MyClass.anotherbit.cs、MyClass.lastoneipromise.csなど? –

+0

私はこれを個人的に行っていませんが、おそらくVisual Studio 2008で実装していますか? – Patrik

+1

私はテストしましたが、Visual Studioではこれが自動的に行われません。 – Patrik

18

を行うために、私は手に本を持っていないが、フレームワークの設計ガイドラインがpublic内部クラスを使用して提案したいくつかは、見た後クライアントがクラス名を参照する必要がないかぎりです。 private内部クラスはうまくいきます。誰にも気付かないでしょう。

悪い:ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();

グッド:あなたの大きなクラスについてlistView.Items.Add(...);

:それは小さなクラス、機能の特定の一枚とそれぞれに一般的にこのような価値のあるスプリット何か。最初はそれを分解するのは難しいですが、私はあなたの人生を後でもっと楽にしてくれると予測しています。

+6

本の中のいくつかのポイント(102ページ)...ネストされたタイプは控えめに使用する必要があります。ネストされた型は、親の型の実装のdeatilsのモデリングに最適です。エンドユーザはネストされた型を宣言する必要はほとんどなく、インスタンス化することはほとんどありません。グループ化にネストされた型を使用しない - 名前空間を使用する。公的なネスト型を避ける – STW

+0

非常に明確で分かりやすい、ありがとう。 – katmanco

3

私は個人的にファイルごとに1つのクラスを持ち、そのファイルの一部として内部クラスを持っています。内部クラスは(ほとんど常に)プライベートであるべきで、クラスの実装の詳細だと私は信じています。別のファイルにそれらを持つことは、物事を混乱させる、IMO。

コード領域を使用して内部クラスをラップし、その詳細を非表示にすると、この場合はうまく動作し、ファイルの操作が難しくなります。コード領域は内部クラスを「隠し」に保ちます。それは私的な実装の詳細なので、それは私にとっては大丈夫です。

2

個人的には、クラス内でのみ使用される概念や操作の一部をカプセル化するために内部クラスを使用します。こうすることで、私はそのクラスの非公式APIを汚染せず、apiをきれいでコンパクトに保ちます。

部分クラスを利用して、これらの内部クラスの定義を別のファイルに移動して、より良いオルガン化を行うことができます。 VSは、ASP.NET、WinFormフォームなどのテンプレート化された項目の一部を除いて、自動的に部分クラスファイルをグループ化しません。プロジェクトファイルを編集して変更を加える必要があります。そこにある既存のグループの1つを見て、それがどのように行われているかを見ることができます。ソリューションエクスプローラで部分クラスファイルをグループ化できるようにするマクロがいくつかあると思います。そのような獣を構築する方法のみについて

6

...

あなたは、メインクラスと入れ子になったクラスを分割する部分クラスを使用することができます。そうするときは、ファイルに適切な名前を付けるようにして、何が起きているのかがはっきりするようにすることをお勧めします。

// main class in file Outer.cs 
namespace Demo 
{ 
    public partial class Outer 
    { 
    // Outer class 
    } 
} 

// nested class in file Outer.Nested1.cs 
namespace Demo 
{ 
    public partial class Outer 
    { 
    private class Nested1 
    { 
     // Nested1 details 
    } 
    } 
} 

多くの場合同じように、(明示的な)インターフェイスがそれぞれのファイルに表示されることがよくあります。例えばOuter.ISomeInterface.csではなく、エディタのデフォルト値#regionを使用してください。

あなたのプロジェクトのファイル構造は、我々はそれがビルダーパターンの変化のためだ、これをやっている通常

 
    /Project/Demo/ISomeInterface.cs 
    /Project/Demo/Outer.cs 
    /Project/Demo/Outer.Nested1.cs 
    /Project/Demo/Outer.ISomeInterface.cs 

のように見え始めます。

0

私の意見では、内部クラスは、必要な場合は、小さくして、そのクラスだけで内部的に使用する必要があります。 .NETフレームワークでRelfectorを使用している場合は、その目的のためだけに多く使用されています。

内部クラスが大きすぎる場合は、保守性のためにのみ、別のクラス/コードファイルに置き換えてください。誰かが内部クラスの内部クラスを使用することは素晴らしいアイデアだと思っていた既存のコードをサポートする必要があります。その結果、内部クラス階層は4〜5レベルの深さで実行されました。言うまでもなく、コードは侵入不可能であり、何を見ているのか理解するのに時間がかかります。ここで

0

(いくつかのユニットテストを追加しました)あなたは、その使用のアイデアを与える可能性が入れ子になったクラスの実用的な例を参照してください

namespace CoreLib.Helpers 
{ 
    using System; 
    using System.Security.Cryptography; 

    public static class Rnd 
    { 
     private static readonly Random _random = new Random(); 

     public static Random Generator { get { return _random; } } 

     static Rnd() 
     { 
     } 

     public static class Crypto 
     { 
      private static readonly RandomNumberGenerator _highRandom = RandomNumberGenerator.Create(); 

      public static RandomNumberGenerator Generator { get { return _highRandom; } } 

      static Crypto() 
      { 
      } 

     } 

     public static UInt32 Next(this RandomNumberGenerator value) 
     { 
      var bytes = new byte[4]; 
      value.GetBytes(bytes); 

      return BitConverter.ToUInt32(bytes, 0); 
     } 
    } 
} 

[TestMethod] 
public void Rnd_OnGenerator_UniqueRandomSequence() 
{ 
    var rdn1 = Rnd.Generator; 
    var rdn2 = Rnd.Generator; 
    var list = new List<Int32>(); 
    var tasks = new Task[10]; 
    for (var i = 0; i < 10; i++) 
    { 
     tasks[i] = Task.Factory.StartNew((() => 
     { 
      for (var k = 0; k < 1000; k++) 
      { 
       lock (list) 
       { 
        list.Add(Rnd.Generator.Next(Int32.MinValue, Int32.MaxValue)); 
       } 
      } 
     })); 
    } 
    Task.WaitAll(tasks); 
    var distinct = list.Distinct().ToList(); 
    Assert.AreSame(rdn1, rdn2); 
    Assert.AreEqual(10000, list.Count); 
    Assert.AreEqual(list.Count, distinct.Count); 
} 

[TestMethod] 
public void Rnd_OnCryptoGenerator_UniqueRandomSequence() 
{ 
    var rdn1 = Rnd.Crypto.Generator; 
    var rdn2 = Rnd.Crypto.Generator; 
    var list = new ConcurrentQueue<UInt32>(); 
    var tasks = new Task[10]; 
    for (var i = 0; i < 10; i++) 
    { 
     tasks[i] = Task.Factory.StartNew((() => 
     { 
      for (var k = 0; k < 1000; k++) 
      { 
        list.Enqueue(Rnd.Crypto.Generator.Next()); 
      } 
     })); 
    } 
    Task.WaitAll(tasks); 
    var distinct = list.Distinct().ToList(); 
    Assert.AreSame(rdn1, rdn2); 
    Assert.AreEqual(10000, list.Count); 
    Assert.AreEqual(list.Count, distinct.Count); 
} 
関連する問題