C#での内部クラスの使用と構造に関するベストプラクティスは何ですか。
たとえば、非常に大きな基本クラスと2つの大きな内部クラスがある場合、それらを別々の(部分クラス)コードファイルに分割するか、1つの非常に扱いにくいコードファイルのままにする必要がありますか?
また、パブリック継承された内部クラスを持つ抽象クラスを持つことは悪い習慣ですか?
通常、私は次の2つの目的のいずれかのために内部クラスを予約します。
親クラスから派生するパブリッククラス。親クラスは、1つ以上の抽象メソッドを持つ抽象基本実装であり、各サブクラスは、特定の実装を提供する実装です。 フレームワークの設計とガイドラインを読んだ後、これは「回避」とマークされているのがわかりますが、列挙型と同様のシナリオで使用します。ただし、おそらく悪い印象も与えます
内部クラスはプライベートであり、ビジネスロジックの単位であるか、他のクラスで使用または使用されると基本的に壊れる方法で親クラスに密結合されます。
他のすべての場合については、コンシューマ/論理親と同じ名前空間と同じアクセシビリティレベルにそれらを維持しようとします-多くの場合、「メイン」クラスよりやや親しみにくい名前を付けます。
大規模なプロジェクトでは、最初または主な目的が論理的に見えるという理由だけで、最初に強く結合されたコンポーネントを最初に構築することにどれほどの頻度で驚かされるでしょうか?他のコンポーネントがそれを消費できるように、クラスを公開しても害はほとんどありません。
編集サブクラスについて話しているとしても、それらは多かれ少なかれ、よく設計された疎結合コンポーネントでなければならないことに注意してください。それらがプライベートであり、外部の世界から見えない場合でも、クラス間の最小の「表面領域」を維持することで、将来の拡張または変更のためのコードの保守性が大幅に容易になります。
手元にある本はありませんが、フレームワーク設計ガイドラインでは、クライアントがクラス名を参照する必要がない限り、public
内部クラスの使用を推奨しています。 private
内部クラスは問題ありません。これらに気付く人はいません。
悪い:ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();
良い:listView.Items.Add(...);
大規模なクラスについて:このようなものを、それぞれに1つの特定の機能を持つ小さなクラスに分割することは、一般的に価値があります。最初に分解するのは難しいですが、後であなたの人生を楽にするでしょう...
通常、内部クラスはプライベートであり、それらを含むクラスだけが使用できるようにする必要があります。それらの内部クラスが非常に大きい場合、それらは独自のクラスである必要があることを示唆します。
通常、大きな内部クラスを取得するのは、内部クラスがそれを含むクラスに密結合されており、プライベートメソッドにアクセスする必要があるためです。
これはかなり主観的だと思いますが、おそらく「ホスト」クラスを部分的にすることで、それらを別々のコードファイルに分割します。
このようにすることで、 プロジェクトファイルの編集 でさらに概要を取得し、Windowsフォームのデザイナークラスのようにファイルグループを作成できます。これを自動的に行うVisual Studioアドインを見たことがあると思いますが、どこで覚えているかはわかりません。
編集:
少し調べたところ、これを行うためのVisual StudioアドインがVSCommandsと呼ばれていました。
そのような獣をどのように構築するかについてのみ...
部分クラスを使用して、メインクラスとネストされたクラスを分割できます。そうするときは、ファイルに適切な名前を付けることをお勧めします。
_// 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
_ ingではなく_#region
_。
プロジェクトのファイル構造は次のようになります
/Project/Demo/ISomeInterface.cs /Project/Demo/Outer.cs /Project/Demo/Outer.Nested1.cs /Project/Demo /Outer.ISomeInterface.cs
通常、これを行うのは、Builderパターンのバリエーションです。
個人的には、ファイルごとに1つのクラスを用意し、そのファイルの一部として内部クラスを用意します。内部クラスは通常(ほぼ常に)プライベートである必要があり、クラスの実装の詳細であると思います。それらを別のファイルに入れると、IMOは混乱します。
コード領域を使用して内部クラスをラップし、その詳細を非表示にすると、この場合はうまく機能し、ファイルの操作が難しくなります。コード領域は内部クラスを「非表示」に保ちます。これはプライベートな実装の詳細であるため、私には問題ありません。
私は個人的には内部クラスを使用して、クラス内でのみ内部的に使用される概念と操作の一部をカプセル化しています。このようにして、そのクラスの非公開APIを汚染せず、APIをクリーンでコンパクトに保ちます。
部分クラスを利用して、これらの内部クラスの定義を別のファイルに移動し、組織化を改善できます。 VSは、ASP.NET、WinFormフォームなどのテンプレート化されたアイテムの一部を除いて、部分クラスファイルを自動的にグループ化しません。プロジェクトファイルを編集し、そこに変更を加える必要があります。そこにある既存のグループの1つを見て、それがどのように行われるかを確認できます。ソリューションエクスプローラーで部分クラスファイルをグループ化できるマクロがいくつかあると思います。
私の意見では、内部クラスは、必要に応じて、小さく保ち、そのクラスのみが内部で使用する必要があります。 .NETフレームワークでRelfectorを使用すると、その目的のためだけにRelfectorが多く使用されることがわかります。
内部クラスが大きくなりすぎた場合は、保守性を確保するためだけに、どういうわけかそれらを別のクラス/コードファイルに移動します。内部クラス内で内部クラスを使用するのが素晴らしいアイデアだと誰かが思った既存のコードをサポートする必要があります。その結果、4〜5レベルの深さで実行される内部クラス階層ができました。言うまでもなく、コードは突き通せず、何を見ているのかを理解するには時間がかかります。
ここでは、それらの使用法のアイデアを提供できるネストされたクラスの実用的な例を示します(いくつかの単体テストが追加されています)。
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);
}