クラス内にのようなブールを作成すると、bool check
デフォルトでfalseになります。
メソッド内でbool check
(クラス内ではなく)同じboolを作成すると、「未割り当てのローカル変数チェックの使用」というエラーが発生します。どうして?
クラス内にのようなブールを作成すると、bool check
デフォルトでfalseになります。
メソッド内でbool check
(クラス内ではなく)同じboolを作成すると、「未割り当てのローカル変数チェックの使用」というエラーが発生します。どうして?
回答:
YuvalとDavidの答えは基本的に正しいです。まとめると:
Davidの回答のコメント投稿者は、静的分析を介して未割り当てフィールドの使用を検出できない理由を尋ねます。これは、この回答で拡張したいポイントです。
まず、ローカルまたはその他の変数について、変数が割り当てられているか割り当てられていないかを正確に判断することは実際には不可能です。検討してください:
bool x;
if (M()) x = true;
Console.WriteLine(x);
「xは割り当てられていますか?」という質問 「M()はtrueを返すか?」と同等です。さて、Fermatの最終定理がElephy Gajillion未満のすべての整数に対して真である場合はM()が真を返し、そうでない場合は偽を返すとします。xが確実に割り当てられているかどうかを判断するために、コンパイラーは基本的にフェルマーの最終定理の証明を作成する必要があります。コンパイラはそれほどスマートではありません。
それでは、コンパイラは、地元の人々のための代わりに行うことは実装しているアルゴリズムであり、高速、かつ過大評価地元が確実に割り当てられていないとき。つまり、あなたと私がそれを知っていても、「このローカルが割り当てられていることを証明できません」という偽陽性がいくつかあります。例えば:
bool x;
if (N() * 0 == 0) x = true;
Console.WriteLine(x);
N()が整数を返すとします。あなたと私はN()* 0が0になることを知っていますが、コンパイラはそれを知りません。(注:C#2.0コンパイラーはそれを知っていましたが、仕様ではコンパイラーがそれを知っているとは言われていないため、その最適化を削除しました。)
よし、これまでに何を知っているの?地元の人が正確な答えを得るのは現実的ではありませんが、割り当てられていないことを安く過大評価して、「不明確なプログラムを修正させる」という面で誤ったかなり良い結果を得ることができます。それは良い。フィールドに同じことをしませんか?つまり、安く過大評価する明確な割り当てチェッカーを作成しますか?
さて、ローカルを初期化する方法はいくつありますか?メソッドのテキスト内で割り当てることができます。メソッドのテキストのラムダ内に割り当てることができます。そのラムダが呼び出されることはないため、これらの割り当ては関係ありません。または、「out」として別のメソッドに渡すこともできます。その時点で、メソッドが正常に戻るときに割り当てられると想定できます。これらは、ローカルが割り当てられる非常に明確なポイントであり、ローカルが宣言されているのと同じメソッド内にあります。ローカルの明確な割り当てを決定するには、ローカル分析のみが必要です。メソッドは短くなる傾向があります-メソッド内のコードが100万行をはるかに下回ります-したがって、メソッド全体の分析は非常に高速です。
では、フィールドについてはどうですか?もちろん、フィールドはコンストラクタで初期化できます。またはフィールド初期化子。または、コンストラクターはフィールドを初期化するインスタンスメソッドを呼び出すことができます。または、コンストラクターはフィールドを初期化する仮想メソッドを呼び出すことができます。または、コンストラクターは、フィールドを初期化する別のクラス(ライブラリーにある可能性がある)のメソッドを呼び出すことができます。静的フィールドは、静的コンストラクターで初期化できます。静的フィールドは、他の静的コンストラクターによって初期化できます。
基本的に、フィールドの初期化子は、まだ作成されていないライブラリで宣言される仮想メソッド内を含め、プログラム全体のどこにでも配置できます。
// Library written by BarCorp
public abstract class Bar
{
// Derived class is responsible for initializing x.
protected int x;
protected abstract void InitializeX();
public void M()
{
InitializeX();
Console.WriteLine(x);
}
}
このライブラリをコンパイルするのはエラーですか?はいの場合、BarCorpはどのようにしてバグを修正するのですか?xにデフォルト値を割り当てることによって?しかし、それはコンパイラがすでに行っていることです。
このライブラリが合法であるとします。FooCorpが書いた場合
public class Foo : Bar
{
protected override void InitializeX() { }
}
であることは誤り?コンパイラはそれをどのように理解するはずですか?唯一の方法は、実行時の仮想メソッドの選択を含むパスを含む、プログラムを介したすべての可能なパス上のすべてのフィールドの初期化静的を追跡するプログラム全体の分析を行うことです。この問題は、任意に難しい場合があります。何百万もの制御パスのシミュレートされた実行が含まれる場合があります。ローカル制御フローの分析にはマイクロ秒かかり、メソッドのサイズによって異なります。グローバル制御フローの分析は、プログラム内のすべてのメソッドとすべてのライブラリーの複雑さに依存するため、数時間かかる場合があります。
それでは、プログラム全体を分析する必要がなく、さらに深刻に過大評価するより安価な分析をしてみませんか?さて、実際にコンパイルする正しいプログラムを書くことをそれほど難しくしなくても機能するアルゴリズムを提案します。設計チームはそれを考慮できます。私はそのようなアルゴリズムを知りません。
現在、コメンターは「コンストラクターがすべてのフィールドを初期化することを要求する」ことを提案しています。それは悪い考えではありません。実際、C#がstructsの機能を既に持っているのは、それほど悪くない考えです。structコンストラクターは、トラクターが正常に戻るまでにすべてのフィールドを確実に割り当てる必要があります。デフォルトのコンストラクターは、すべてのフィールドをデフォルト値に初期化します。
クラスはどうですか?さて、コンストラクタがフィールドを初期化したことをどうやって知っていますか?ctorは仮想メソッドを呼び出してフィールドを初期化することができ、これで以前と同じ位置に戻ります。構造体には派生クラスはありません。クラスかもしれません。すべてのフィールドを初期化するコンストラクターを含む必要がある抽象クラスを含むライブラリーはありますか?抽象クラスは、フィールドを初期化する必要がある値をどのようにして知るのですか?
Johnは、フィールドが初期化される前に、ctor内のメソッドの呼び出しを単に禁止することを提案しています。要約すると、私たちのオプションは次のとおりです。
設計チームは3番目のオプションを選択しました。
bool x;
同等にしbool x = false;
てみませんか?
メソッド内でbool check(クラス内ではなく)を作成すると、「未割り当てのローカル変数チェックの使用」というエラーが発生します。どうして?
コンパイラはあなたが間違いを犯さないようにしようとしているからです。
変数を初期化してfalse
、この特定の実行パスで何かを変更しますか?おそらくそうでdefault(bool)
はない、とにかく考えることは間違っていますが、これが起こっていることをあなたに気づかせるように強制しています。.NET環境では、値がデフォルトに初期化されるため、「ガベージメモリ」にアクセスできません。ただし、これが参照型であり、初期化されていない(null)値をnull以外を期待するメソッドに渡し、実行時にNREを取得するとします。コンパイラは単にこれを防止しようと試みており、これによりbool b = false
ステートメントが発生する場合があるという事実を受け入れます。
Eric Lippert がブログ投稿でこれについて語っています:
私たちがこれを違法にしたい理由は、ローカル変数がガベージに初期化され、あなたをガベージから保護したいからです。実際には、ローカルをデフォルト値に自動的に初期化します。(CおよびC ++プログラミング言語はそうではなく、陽気に初期化されていないローカルからガベージを読み取ることを許可します。)むしろ、そのようなコードパスの存在はおそらくバグであるためです。質の落とし穴; あなたはそのバグを書くために一生懸命働かなければならないはずです。
なぜこれがクラスフィールドに適用されないのですか?まあ、私はどこかで線を引く必要があると思います。ローカル変数の初期化は、クラスフィールドとは対照的に、診断して正しく理解するのがはるかに簡単です。コンパイラはこれを行うことができますが、クラスの各フィールドが初期化されているかどうかを評価するために、実行する必要があるすべてのチェック(クラスコード自体に依存しないものもある)を考えてください。私はコンパイラの設計者ではありませんが、考慮に入れられる多くのケースがあり、同様にタイムリーな方法で行わなければならないので、それは間違いなく困難になるでしょう。設計、作成、テスト、および展開する必要があるすべての機能について、実行する労力とは対照的に、これを実装する価値は価値がなく複雑です。
ローカル変数で初期化が必要なのにフィールドでは不要なのはなぜですか?
短い答えは、初期化されていないローカル変数にアクセスするコードは、静的分析を使用して、信頼できる方法でコンパイラーによって検出できるということです。これはフィールドの場合ではありませんが。したがって、コンパイラは最初のケースを強制しますが、2番目のケースは強制しません。
ローカル変数に初期化が必要なのはなぜですか?
Eric Lippertが説明したように、これはC#言語の設計上の決定にすぎません。CLRと.NET環境では必要ありません。たとえば、VB.NETは、初期化されていないローカル変数で問題なくコンパイルされますが、実際には、CLRはすべての初期化されていない変数をデフォルト値に初期化します。
同じことがC#でも発生する可能性がありますが、言語設計者は選択しませんでした。その理由は、初期化された変数はバグの大きな原因であるため、初期化を強制することにより、コンパイラーは偶発的なミスを減らすのに役立ちます。
フィールドが初期化を必要としないのはなぜですか?
では、なぜこのような明示的な初期化がクラス内のフィールドで発生しないのでしょうか。明示的な初期化が構築中に発生する可能性があるという理由だけで、プロパティはオブジェクト初期化子によって呼び出されるか、またはイベントのかなり後に呼び出されるメソッドによっても。コンパイラーは、静的分析を使用して、コード内のすべての可能なパスが、変数が私たちの前で明示的に初期化されることにつながるかどうかを判断することはできません。コンパイルできない有効なコードが開発者に残される可能性があるため、それを間違えるのは面倒です。そのため、C#はそれを強制せず、CLRは明示的に設定されていない場合、フィールドを自動的にデフォルト値に初期化します。
コレクション型についてはどうですか?
C#によるローカル変数の初期化の実施は制限されており、開発者の注意を引くものです。次の4行のコードについて考えてみます。
string str;
var len1 = str.Length;
var array = new string[10];
var len2 = array[0].Length;
初期化されていない文字列変数を読み取ろうとしているため、コードの2行目はコンパイルされません。ただし、コードの4行目array
は、初期化されたように問題なくコンパイルされますが、デフォルト値しかありません。文字列のデフォルト値はnullであるため、実行時に例外が発生します。ここでスタックオーバーフローに時間を費やした人なら誰でも、この明示的/暗黙的な初期化の不整合が原因で、「「オブジェクト参照がオブジェクトのインスタンスに設定されていません」というエラーが発生するのはなぜですか?」質問。
public interface I1 { string str {get;set;} }
メソッドの単純なケースを考えてみましょうint f(I1 value) { return value.str.Length; }
。これがライブラリーに存在する場合、コンパイラーはそのライブラリーのリンク先を認識できないため、がのset
前に呼び出されたかどうか、get
バッキングフィールドは明示的に初期化されない可能性がありますが、そのようなコードをコンパイルする必要があります。
f
。コンストラクタのコンパイル時に生成されます。初期化されていない可能性のあるフィールドを持つコンストラクターを残すと、エラーになります。すべてのフィールドを初期化する前に、クラスメソッドとゲッターの呼び出しに制限が必要な場合もあります。
上記の良い答えですが、私が長い答え(私のように)を読むのが面倒な人には、もっと簡単で短い答えを投稿したいと思いました。
class Foo {
private string Boo;
public Foo() { /** bla bla bla **/ }
public string DoSomething() { return Boo; }
}
プロパティBoo
はコンストラクタで初期化されている場合とされていない場合があります。したがって、それが検出されても、初期化されているとreturn Boo;
は想定していません。単にエラーを抑制します。
public string Foo() {
string Boo;
return Boo; // triggers error
}
{ }
文字コードのブロックの範囲を定義します。コンパイラーは、これらの{ }
ブロックの分岐を追跡して、内容を追跡します。初期化されていないことが簡単にわかりBoo
ます。その後、エラーが発生します。
このエラーは、ソースコードを安全にするために必要なコードの行数を減らすために導入されました。エラーがなければ、上記は次のようになります。
public string Foo() {
string Boo;
/* bla bla bla */
if(Boo == null) {
return "";
}
return Boo;
}
マニュアルから:
C#コンパイラでは、初期化されていない変数を使用できません。初期化されていない可能性がある変数の使用をコンパイラが検出すると、コンパイラエラーCS0165が生成されます。詳細については、「フィールド(C#プログラミングガイド)」を参照してください。このエラーは、特定のコードでは発生しない場合でも、コンパイラが未割り当ての変数を使用する可能性のある構造に遭遇したときに生成されることに注意してください。これにより、明確な割り当てのための過度に複雑なルールが不要になります。
リファレンス:https : //msdn.microsoft.com/en-us/library/4y7h161d.aspx