C# のネストされたクラスについて理解しようとしています。ネストされたクラスは、別のクラス内で定義されたクラスであることを理解しています。私が得られないのは、これを行う必要がある理由です。
8 に答える
私が特に気に入っているパターンは、ネストされたクラスとファクトリ パターンを組み合わせることです。
public abstract class BankAccount
{
private BankAccount() {} // prevent third-party subclassing.
private sealed class SavingsAccount : BankAccount { ... }
private sealed class ChequingAccount : BankAccount { ... }
public static BankAccount MakeSavingAccount() { ... }
public static BankAccount MakeChequingAccount() { ... }
}
このようにクラスを入れ子にすることで、第三者が独自のサブクラスを作成できないようにしています。bankaccount オブジェクトで実行されるすべてのコードを完全に制御できます。そして、すべてのサブクラスは、基本クラスを介して実装の詳細を共有できます。
通常、その目的は、ネストされたクラスのスコープを制限することだけです。通常のクラスと比較して、ネストされたクラスには、private
修飾子の追加の可能性があります (protected
もちろん)。
基本的に、このクラスを「親」クラス内からのみ使用する必要がある場合 (スコープの観点から)、通常はネストされたクラスとして定義するのが適切です。このクラスをassembly/library なしで使用する必要がある場合は、通常、2 つのクラス間に概念的な関係があるかどうかに関係なく、別の (兄弟) クラスとして定義する方がユーザーにとって便利です。public
親クラス内にネストされたクラスを作成することは技術的に可能public
ですが、私の意見では、これを実装するのが適切なことはめったにありません。
入れ子になったクラスはprivate
、protected
および とprotected internal
一緒にアクセス修飾子public
を持つことができますinternal
。
たとえば、オブジェクトGetEnumerator()
を返すメソッドを実装しています。IEnumerator<T>
消費者は、オブジェクトの実際のタイプを気にしません。彼らがそれについて知っているのは、それがそのインターフェースを実装していることだけです。返したいクラスには直接的な用途がありません。そのクラスをネストされたクラスとして宣言し、そのprivate
インスタンスを返すことができます (これは実際に C# コンパイラが反復子を実装する方法です)。
class MyUselessList : IEnumerable<int> {
// ...
private List<int> internalList;
private class UselessListEnumerator : IEnumerator<int> {
private MyUselessList obj;
public UselessListEnumerator(MyUselessList o) {
obj = o;
}
private int currentIndex = -1;
public int Current {
get { return obj.internalList[currentIndex]; }
}
public bool MoveNext() {
return ++currentIndex < obj.internalList.Count;
}
}
public IEnumerator<int> GetEnumerator() {
return new UselessListEnumerator(this);
}
}
私が得られないのは、なぜ私がこれをする必要があるのかということです
私はあなたがこれをする必要は決してないと思います。このようなネストされたクラスが与えられた...
class A
{
//B is used to help implement A
class B
{
...etc...
}
...etc...
}
...このように、いつでも内部/ネストされたクラスをグローバルスコープに移動できます...
class A
{
...etc...
}
//B is used to help implement A
class B
{
...etc...
}
ただし、BがAの実装を支援するためにのみ使用される場合、Bを内部/ネストされたクラスにすることには2つの利点があります。
- グローバルスコープを汚染しません(たとえば、Aを見ることができるクライアントコードはBクラスが存在することさえ知らない)
- Bのメソッドは、暗黙的にAのプライベートメンバーにアクセスできます。一方、BがA内にネストされていない場合、Aのメンバーが内部またはパブリックでない限り、BはAのメンバーにアクセスできません。ただし、これらのメンバーを内部または公開にすると、他のクラス(Bだけでなく)にも公開されます。したがって、代わりに、Aのメソッドをプライベートに保ち、Bをネストされたクラスとして宣言することにより、Bがそれらにアクセスできるようにします。C ++を知っている場合、これは、C#では、ネストされたすべてのクラスが、それらが含まれているクラスの「フレンド」であると言うようなものです(また、クラスをネストされたものとして宣言することが、C#でフレンドシップを宣言する唯一の方法です。 C#には
friend
キーワードがありません)。
BがAのプライベートメンバーにアクセスできると言うとき、それはBがAへの参照を持っていると仮定しています。ネストされたクラスはこのように宣言されることが多いため、これはよく行われます...
class A
{
//used to help implement A
class B
{
A m_a;
internal B(A a) { m_a = a; }
...methods of B can access private members of the m_a instance...
}
...etc...
}
...そしてこのようなコードを使用してAのメソッドから構築されます...
//create an instance of B, whose implementation can access members of self
B b = new B(this);
Mehrdadの返信で例を見ることができます。
public ネストされたメンバーの有効な使用方法もあります...
ネストされたクラスは、外部クラスのプライベート メンバーにアクセスできます。したがって、これが正しい方法であるシナリオは、Comparer を作成する場合 (つまり、IComparer インターフェイスを実装する場合) です。
この例では、FirstNameComparer はプライベート _firstName メンバーにアクセスできますが、クラスが別のクラスである場合はアクセスできません...
public class Person
{
private string _firstName;
private string _lastName;
private DateTime _birthday;
//...
public class FirstNameComparer : IComparer<Person>
{
public int Compare(Person x, Person y)
{
return x._firstName.CompareTo(y._firstName);
}
}
}
クラス内から返されるインターフェイスを実装すると便利な場合もありますが、そのインターフェイスの実装は外部から完全に隠されている必要があります。
例として、C# に yield が追加される前は、列挙子を実装する 1 つの方法は、列挙子の実装をコレクション内のプライベート クラスとして配置することでした。これにより、コレクションのメンバーに簡単にアクセスできますが、外部の世界は、これがどのように実装されているかの詳細を必要とせず、見る必要もありません。
ネストされたクラスは、公開してはならない内部の詳細を実装するのに非常に役立ちます。Reflector を使用して Dictionary<Tkey,TValue> や Hashtable などのクラスをチェックすると、いくつかの例が見つかります。
これは、ネストされたクラスを使用する場合の良い例でしょうか?
// ORIGINAL
class ImageCacheSettings { }
class ImageCacheEntry { }
class ImageCache
{
ImageCacheSettings mSettings;
List<ImageCacheEntry> mEntries;
}
と:
// REFACTORED
class ImageCache
{
Settings mSettings;
List<Entry> mEntries;
class Settings {}
class Entry {}
}
PS: どのアクセス修飾子を適用する必要があるかは考慮していません (private、protected、public、internal)