キャスリーン・ドラードの2008年のブログ投稿で、彼女は.netでネストされたクラスを使用する興味深い理由を示しています。ただし、FxCopはネストされたクラスが好きではないと彼女は述べています。FxCopルールを書いている人は愚かではないと思いますので、その立場には理由があるはずですが、私はそれを見つけることができませんでした。
14 に答える
ネストするクラスがそれを囲むクラスにのみ役立つ場合は、ネストされたクラスを使用します。たとえば、ネストされたクラスを使用すると、次のようなものを記述できます(簡略化)。
public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
}
クラスの完全な定義を1つの場所で行うことができ、クラスがどのように機能するかを定義するためにPIMPLフープを飛び越える必要はなく、外部の世界は実装の何も見る必要がありません。
TreeNodeクラスが外部の場合、それを使用するには、すべてのフィールドを作成するかpublic
、一連のget/set
メソッドを作成する必要があります。外の世界では、別のクラスが彼らのインテリセンスを汚染しているでしょう。
ネストされたクラスを使用する理由 ネストされたクラスを使用する理由はいくつかありますが、その中には次のものがあります。
- これは、1か所でのみ使用されるクラスを論理的にグループ化する方法です。
- それはカプセル化を増加させます。
- ネストされたクラスは、より読みやすく保守しやすいコードにつながる可能性があります。
クラスの論理グループ化-クラスが他の1つのクラスにのみ役立つ場合は、そのクラスにそのクラスを埋め込み、2つを一緒に保つことが論理的です。このような「ヘルパークラス」をネストすると、パッケージがより合理化されます。
カプセル化の増加-2つのトップレベルクラスAとBを検討します。ここで、Bは、そうでなければプライベートと宣言されるAのメンバーにアクセスする必要があります。クラスBをクラスA内に隠すことにより、Aのメンバーはプライベートとして宣言され、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();
}
}
用途によります。パブリックネストクラスを使用することはめったにありませんが、常にプライベートネストクラスを使用します。プライベートネストクラスは、親の内部でのみ使用することを目的としたサブオブジェクトに使用できます。この例は、HashTableクラスにデータを内部的にのみ格納するためのプライベートEntryオブジェクトが含まれている場合です。
クラスが(外部から)呼び出し元によって使用されることを意図している場合、私は通常、それを別個のスタンドアロンクラスにするのが好きです。
上記の他の理由に加えて、ネストされたクラスを使用するだけでなく、実際にはパブリックのネストされたクラスを使用する理由がもう 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 ソリューション エクスプローラーのファイルは、次のように編成されます。
Entity.cs
+ Entity.BaseDataObject.cs
+ Entity.BaseDataObjectList.cs
+ Entity.IBaseBusiness.cs
+ Entity.IBaseDataAccess.cs
そして、次のような汎用名前空間を実装します。
ユーザー.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コメントのようなネストされたクラスの使用は、設計が悪いことを示すネストされたクラスが大量にある場合を除いて、許容できることがわかりました。
ネストされたクラスは、次のニーズに使用できます。
- データの分類
- メインクラスのロジックが複雑で、クラスを管理するために下位オブジェクトが必要な場合
- クラスの状態と存在が完全に包含クラスに依存している場合
ネストされたクラスについてまだ言及されていない別の用途は、ジェネリック型の分離です。たとえば、さまざまな数のパラメーターを持つメソッドと、それらのパラメーターのいくつかの値を取り、より少ないパラメーターでデリゲートを生成できる静的クラスのジェネリック ファミリーが必要であるとします。たとえば、 を取り、 ;として 3.5 を渡して提供されたアクションを呼び出すAction<string, int, double>
を生成できる静的メソッドが必要な場合があります。an を受け取り、を生成し、およびとして渡すことができる静的メソッドが必要な場合もあります。一般的なネストされたクラスを使用すると、メソッド呼び出しを次のように調整できます。String<string, int>
double
Action<string, int, double>
Action<string>
7
int
5.3
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);
入れ子になったジェネリック型を使用すると、どのデリゲートが全体的な型記述のどの部分に適用できるかを知ることができます。
単一のクラスに固有の例外をネストするのが好きです。他の場所から決して投げ出されないもの。
例えば:
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 のずんぐりした小さな例外クラスでいっぱいにならないようにすることができます。
nawfalが Abstract Factory パターンの実装について述べたように、そのコードは、Abstract Factoryパターンに基づくクラス クラスター パターンを実現するために拡張できます。
この概念の理解に基づいて、クラスが概念的に互いに関連している場合にこの機能を使用できます。つまり、それらのいくつかは、ビジネス ロジックを完成させるためにルート オブジェクトを集約するのに役立つ、DDD の世界に存在するエンティティのようなビジネスの 1 つのアイテムです。
明確にするために、例を使ってこれを示します。
Order と OrderItem のような 2 つのクラスがあるとします。order クラスでは、すべての orderItems を管理し、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();
}
}