でキャスリーンDollardの2008年のブログ記事、彼女は.NETで、ネストされたクラスを使用するには、興味深い理由を提示しています。ただし、FxCopはネストされたクラスを好まないことにも言及しています。私はFxCopルールを書いている人は愚かではないと想定しているので、その立場の背後には理由があるに違いありませんが、私はそれを見つけることができませんでした。
でキャスリーンDollardの2008年のブログ記事、彼女は.NETで、ネストされたクラスを使用するには、興味深い理由を提示しています。ただし、FxCopはネストされたクラスを好まないことにも言及しています。私はFxCopルールを書いている人は愚かではないと想定しているので、その立場の背後には理由があるに違いありませんが、私はそれを見つけることができませんでした。
回答:
ネストしているクラスが、それを囲んでいるクラスだけに役立つ場合は、ネストしたクラスを使用します。たとえば、ネストされたクラスを使用すると、(簡略化された)次のように記述できます。
public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
}
クラスの完全な定義を1か所で行うことができ、クラスがどのように機能するかを定義するためにPIMPLフープにジャンプする必要はありません。また、外部の世界で実装の内容を確認する必要はありません。
TreeNodeクラスが外部の場合、すべてのフィールドを作成するか、それを使用public
するために一連のget/set
メソッドを作成する必要があります。外の世界は彼らのインテリセンスを汚染する別のクラスを持つでしょう。
ネストされたクラスを使用する理由 ネストされたクラスを使用する説得力のある理由がいくつかあります。
クラスの論理的なグループ化-クラスが他の1つのクラスだけに役立つ場合、そのクラスに埋め込み、2つをまとめることが論理的です。このような「ヘルパークラス」をネストすると、パッケージがより合理化されます。
カプセル化の増加-2つの最上位クラスAとBを検討します。Bは、そうでなければプライベートとして宣言されるAのメンバーにアクセスする必要があります。クラスA内でクラスBを非表示にすることにより、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();
}
}
上記の他の理由に加えて、ネストされたクラスを使用するだけでなく、実際にはパブリックなネストされたクラスを使用することを考えることができるもう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つの型間でこれらの型パラメーターの同期を維持する必要があり、面倒でした。一般的な名前空間を使用すると、そのコードははるかに管理しやすく、読みやすくなります。
Katheleenの記事を正しく理解していれば、彼女はEntityCollection <SomeEntity>ではなく、SomeEntity.Collectionを記述できるようにネストされたクラスを使用することを提案しています。私の意見では、タイピングの手間を省くには物議を醸す方法です。実際のアプリケーションコレクションでは実装に多少の違いがあると確信しているため、とにかく個別のクラスを作成する必要があります。クラス名を使用して他のクラスのスコープを制限することは良い考えではないと思います。インテリセンスを汚染し、クラス間の依存関係を強化します。名前空間の使用は、クラスのスコープを制御する標準的な方法です。ただし、@ hazzenコメントのようなネストされたクラスの使用は、デザインが悪い兆候であるネストされたクラスが大量にない限り、許容できることがわかります。
ネストされたクラスを使用して、実装の詳細を隠すことがよくあります。エリック・リペルトの答えの例はこちら:
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
}
ネストされたクラスでまだ言及されていない別の用途は、ジェネリック型の分離です。たとえば、さまざまな数のパラメーターを持つメソッドとそれらのいくつかのパラメーターの値を受け取り、より少ないパラメーターでデリゲートを生成できる静的クラスのいくつかのジェネリックファミリーが必要であるとします。たとえば、3.5をとして渡し、指定されたアクションを呼び出すAction<string, int, double>
a String<string, int>
を取得して生成できる静的メソッドが必要double
です。an Action<string, int, double>
を取得してを生成し、and として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);
ネストされたジェネリック型を使用すると、どのデリゲートが型全体の説明のどの部分に適用できるかを知ることができます。
以下のようnawfal Abstract Factoryパターンの上記実施、そのコードを達成するためにaxtendedすることができ、クラスクラスタパターン抽象Factoryパターンに基づいています。
つまり、単一のクラスに固有の例外をネストするのが好きです。他の場所から決して投げられないもの。
例えば:
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のずんぐりした小さな例外クラスでいっぱいにならないようにするのに役立ちます。
この場合はい:
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);
}
}
}
この概念についての私の理解に基づいて、クラスが概念的に相互に関連している場合にこの機能を使用できます。それらのいくつかは、ビジネスロジックを完了するために集約ルートオブジェクトを支援する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();
}
}