[解決済み] なぜ再帰的なコンストラクタ呼び出しは、無効なC#コードをコンパイルするのでしょうか?
質問
ウェビナー視聴後 Jon SkeetがReSharperを検証する ということで、私は再帰的コンストラクタの呼び出しについて少し遊び始めました。 再帰的なコンストラクタの呼び出しで少し遊んでみたところ、以下のコードが有効な C# コードであることがわかりました(有効というのは、コンパイルできるという意味です)。
class Foo
{
int a = null;
int b = AppDomain.CurrentDomain;
int c = "string to int";
int d = NonExistingMethod();
int e = Invalid<Method>Name<<Indeeed();
Foo() :this(0) { }
Foo(int v) :this() { }
}
ご存知のように、フィールドの初期化はコンパイラによってコンストラクタに移されます。ですから、もしあなたが
int a = 42;
のようなフィールドがある場合、次のようになります。
a = 42
で
全て
となります。しかし、もしコンストラクタが他のコンストラクタを呼び出している場合、呼び出された方だけに初期化コードが存在することになります。
例えば、パラメータを持つコンストラクタがデフォルトのコンストラクタを呼び出す場合、代入されるのは
a = 42
という代入がデフォルトコンストラクタの中だけで行われます。
2番目のケースを説明するために、次のコードを示します。
class Foo
{
int a = 42;
Foo() :this(60) { }
Foo(int v) { }
}
にコンパイルします。
internal class Foo
{
private int a;
private Foo()
{
this.ctor(60);
}
private Foo(int v)
{
this.a = 42;
base.ctor();
}
}
つまり、主な問題は、この質問の最初に与えられた私のコードが、コンパイルされることです。
internal class Foo
{
private int a;
private int b;
private int c;
private int d;
private int e;
private Foo()
{
this.ctor(0);
}
private Foo(int v)
{
this.ctor();
}
}
見ての通り、コンパイラはフィールドの初期化をどこに置くか決められず、結果的にどこにも置かなかったということです。また
base
コンストラクタの呼び出しがないことにも注意してください。もちろん、オブジェクトは生成されませんので、最終的には常に
StackOverflowException
のインスタンスを作ろうとすると
Foo
.
2つ質問があります。
なぜコンパイラは再帰的なコンストラクタの呼び出しを許可するのでしょうか?
なぜそのようなクラス内で初期化されたフィールドに対してコンパイラのそのような動作を観察するのでしょうか?
いくつかの注意事項。
ReSharper
で警告します。
Possible cyclic constructor calls
. さらに、Javaではこのようなコンストラクタ呼び出しはイベントコンパイルされないので、Javaコンパイラはこのシナリオでより制限的です(JonはWebinarでこの情報に言及しました)。
このため、これらの質問はより興味深いものとなっています。Java コミュニティに敬意を表しつつ、C# コンパイラーは 少なくとも より現代的です。
これをコンパイルするには C# 4.0 と C# 5.0 コンパイラを使用し、デコンパイルは ドットピーク .
どのように解決するのですか?
面白い発見がありました。
インスタンスコンストラクタは本当に2種類しかないようです。
-
別のインスタンス コンストラクタをチェーンするインスタンス コンストラクタ。
を連鎖させます。
で、そのインスタンスを
: this( ...)
の構文を使用します。 -
インスタンスコンストラクタをチェーンするインスタンスコンストラクタ
を連鎖させるインスタンスコンストラクタです。
. これは鎖が指定されていないインスタンスコンストラクタも含みます。
: base()
がデフォルトであるためです。
(のインスタンスコンストラクタは無視しました)。
System.Object
というのは特殊なケースで
System.Object
には基底クラスがありません! しかし
System.Object
にはフィールドもありません)。
クラス内に存在するインスタンスフィールドのイニシャライザは、以下の型のすべてのインスタンスコンストラクタのボディの最初にコピーされる必要があります。 2. のインスタンスコンストラクタはありません。 1. は、フィールドの割り当てコードが必要です。
のコンストラクタを解析する必要はないようです。 1. を使用して、サイクルがあるかどうかを確認します。
さて、あなたの例では、次のような状況を与えています。 すべて インスタンスコンストラクタが 1. . この状況では、フィールドのイニシャライザコードはどこにも置く必要がありません。ということで、あまり深く解析されていないようです。
これは、すべてのインスタンスコンストラクタが型
1.
のように、アクセス可能なコンストラクタを持たない基底クラスから派生させることも可能です。ただし、ベースクラスは非シール型である必要があります。例えば、もしあなたが
private
のインスタンスコンストラクタしかないクラスを書いたとしても、 派生されたクラスのインスタンスコンストラクタをすべて
1.
のようになります。しかし、新しいオブジェクトの作成式は、当然ながら決して終了しません。派生クラスのインスタンスを作成するには、"cheat" のようなものを使う必要があります。
System.Runtime.Serialization.FormatterServices.GetUninitializedObject
メソッドのようなものを使用する必要があります。
もう一つの例として この例では
System.Globalization.TextInfo
クラスには
internal
のインスタンスコンストラクタしかありません。しかし、このクラスから他のアセンブリに派生させることは可能です。
mscorlib.dll
以外のアセンブリでこのクラスから派生することができます。
について、最後に
Invalid<Method>Name<<Indeeed()
という構文があります。C#の規則によると、これは次のように読みます。
(Invalid < Method) > (Name << Indeeed())
というのは,左シフト演算子
<<
は小なり演算子
<
と大なり演算子
>
. 後者の2つの演算子は同じ優先順位を持つので、左結合ルールで評価される。もし型が
MySpecialType Invalid;
int Method;
int Name;
int Indeed() { ... }
で、もし
MySpecialType
が導入されると
(MySpecialType, int)
のオーバーロードを導入しました。
operator <
とすると、式は
Invalid < Method > Name << Indeeed()
は合法的で意味のあるものでしょう。
私の意見では、このシナリオでコンパイラが警告を発した方が良いと思います。例えば、以下のようになります。
unreachable code detected
と表示し、IL に決して変換されないフィールド初期化子の行と列番号を指摘することができます。
関連
-
[解決済み】統合マネージドパイプラインモードで適用されないASP.NETの設定が検出された
-
[解決済み】非静的メソッドはターゲットを必要とする
-
[解決済み】名前 'ViewBag' が現在のコンテキストに存在しない - Visual Studio 2015
-
[解決済み] なぜList<T>を継承しないのですか?
-
[解決済み] コンストラクタ内の仮想メンバー呼び出し
-
[解決済み] あるコンストラクタを別のコンストラクタから呼び出す
-
[解決済み】.NETで構造体のデフォルトコンストラクタを定義できないのはなぜですか?
-
[解決済み】C# 5 非同期 CTP:生成されたコードで EndAwait 呼び出しの前に内部の "state" が 0 に設定されるのはなぜですか?
-
[解決済み] アンダースコアを付けるか付けないか、それが問題です。
-
[解決済み] XMLシリアライズ可能なクラスがパラメータなしのコンストラクタを必要とする理由
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】エラー。「戻り値を変更できません」 C#
-
[解決済み】Unity3DでOnTriggerEnterが動作しない件
-
[解決済み】WPFでXamlファイルにコメントを追加する方法は?
-
[解決済み] 'IEnumerable<SelectListItem>' 型の ViewData アイテムで、キーが国であるものは存在しない。
-
[解決済み】C#のequal to演算子でtextとvarcharのデータ型は互換性がない
-
[解決済み】aspNetCore 2.2.0 - AspNetCoreModuleV2 エラー
-
[解決済み】「namespace」なのに「type」のように使われる。
-
VSでscanfエラーを恒久的に解決するには、ソースファイルを作成し、自動的に#define _CRT_SECURE_NO_WARNINGS 1を追加してください。
-
[解決済み】別のスレッドがこのオブジェクトを所有しているため、呼び出し側のスレッドはこのオブジェクトにアクセスできない
-
[解決済み] C# スタックオーバーフロー例外をキャッチする