C# では、クラスが独自の入れ子になったクラスを拡張できないのはなぜですか?
-
06-07-2019 - |
質問
例えば:
public class A : A.B
{
public class B { }
}
これにより、コンパイラからこのエラーが生成されます。
円形の基本クラス依存性 A'と'A.B'を含む
ネストされたクラスは、外部クラスのプライベート メンバーへのアクセスに関する特別なルールを除いて、通常のクラスと同じように動作すると常々思っていましたが、2 つのクラス間で暗黙の継承が発生しているのではないでしょうか?
解決
私の知る限り、暗黙の継承は関係ありません。私はこれで大丈夫だと予想していましたが、A と B が一般的なものであれば奇妙になることは想像できます。
これは仕様のセクション 10.1.4 で指定されています。
クラスBがクラスAから派生した場合、 とするのはコンパイル時のエラーである。 Bに依存する。クラスが直接依存する 直接の基底クラスがある場合は、その基底クラス上で 内のクラスに直接依存する。 その直後にネストされている もし any)。この定義からすると を構成するクラスの完全な集合である。 クラスは の閉鎖は、直接的には 関係にある。
関連するセクションを強調表示しました。
これは、コンパイラがそれを拒否する理由を説明しますが、言語がそれを禁止する理由ではありません。CLIの制限でもあるのかな…
編集:さて、エリック・リッパートから返事がありました。基本的に、それは技術的には可能ですが (CLI にはそれを禁止するものはありません)、次のとおりです。
- コンパイラーでこれを許可することは困難であり、順序とサイクルに関する現在のさまざまな仮定が無効になります。
- これはかなり奇妙な設計上の決定であり、サポートするよりも禁止する方が簡単です。
電子メール スレッドでは、次のようなことが有効になることも指摘されています。
A.B x = new A.B.B.B.B.B.B.B.B.B.B.B.B();
...しかし、B が A から派生した場合、それはすでに (ティニスターが指摘したように) 有効になります。
ネスト + 継承 = 奇妙です...
他のヒント
これは、C#のものではなく、コンパイラのものです。コンパイラの仕事の1つは、クラスをメモリにレイアウトすることです。これは、基本的なデータ型、ポインター、関数ポインター、およびその他のクラスの集まりです。
クラスBのレイアウトがわかるまで、クラスAのレイアウトを構築できません。クラスAのレイアウトが完了するまで、クラスBのレイアウトが何であるかを知ることはできません。循環依存関係。
ネストは、ネストされたタイプがネストタイプの定義の一部である であることを表すことを意図していると思います。その解釈では、コンパイラーがAの定義に到達した時点で、A.Bはまだ定義されておらず、Aの終わりでさえ、A.Bに関してすでに定義されているため、制限は理にかなっています。
私がやろうとしていたことに関する質問について:
基本的に、それ自体と構成関係を持つクラスを作成したかったのですが、含まれるオブジェクトに他のオブジェクトを含めることはしたくないため、多くの" A has-a A has- a A has-a A has-a ...」関係。そのため、当時の私の考えは次のようなことでした:
public class A : A.AA
{
public class AA
{
// All of the class's logic
}
private AA _containedObject;
}
当時はかなり滑らかに見えましたが、振り返ってみると、私はよくわかりません...
Googleを介して大騒ぎしていて、それについての良い議論を見つけられなかったので、ここに投稿したいと思いました。
ただし、 Eric Lippertのブログに投稿入れ子になったインターフェイスを実装するクラスと、入れ子になったクラスを型引数として使用する汎用インターフェイスを実装するクラスの例を示します(コンパイルせず、現在のコンパイラで「バグ」を呼び出します)。これらの例は両方ともインターフェースに関するものなので、ネストされたクラスには特別なルールがあるのではないかと思っていました。そして、それがあるようです。
ネストされたインターフェイスを含む別のクラスから継承することで、(少なくともインターフェイスでは)これを回避できました。 (私のシナリオでは、これらのインターフェースへの参照も返します。)
代わりに:
public class MyClass<T1, T2, T3> :
MyClass<T1, T2, T3>.Interface
where T1 : ...
where T2 : ...
where T3 : ... {
public interface Interface { Interface SomeMethod(); }
Interface Interface.SomeMethod() {
...
}
}
// compile error: Circular base class dependency
次のようなことを行います:
public sealed class MyClassInterfaces<T1, T2, T3>
where T1 : ...
where T2 : ...
where T3 : ... {
public interface Interface { Interface SomeMethod(); }
}
sealed class MyClass<T1, T2, T3> :
MyClassInterfaces<T1, T2, T3>.Interface
where T1 : ...
where T2 : ...
where T3 : ... {
MyClassInterfaces<T1, T2, T3>.Interface
MyClassInterfaces<T1, T2, T3>.Interface.SomeMethod() {
...
}
}
明示的なインターフェイス実装でのさを避けるために、他のクラスから継承することもできますが、ネストされたクラスから継承しようとした場合、両方のクラスから継承できないため、機能しません。
public abstract class MyClassInterfaces<T1, T2, T3>
where T1 : ...
where T2 : ...
where T3 : ... {
public interface Interface { Interface SomeMethod(); }
}
sealed class MyClass<T1, T2, T3> :
MyClassInterfaces<T1, T2, T3>,
MyClassInterfaces<T1, T2, T3>.Interface
where T1 : ...
where T2 : ...
where T3 : ... {
Interface Interface.SomeMethod() {
...
}
}
これは私には意味がありません...存在しないものを拡張しようとしています!!!クラスBはクラスAのスコープ内にのみ存在します。そのため、何らかの継承があると思います。