web-dev-qa-db-ja.com

ローカル変数は初期化を必要とするのにフィールドは必要ないのはなぜですか?

クラス内にbool checkのようなboolを作成すると、デフォルトでfalseになります。

メソッド内でbool check(クラス内ではなく)同じブールを作成すると、「未使用のローカル変数チェックの使用」というエラーが表示されます。どうして?

139
nachime

YuvalとDavidの答えは基本的に正しいです。要約すると:

  • 未割り当てのローカル変数の使用はバグである可能性が高く、これは低コストでコンパイラによって検出できます。
  • 割り当てられていないフィールドまたは配列要素を使用してもバグになる可能性は低く、コンパイラで状態を検出することは困難です。したがって、コンパイラは、フィールドの初期化されていない変数の使用を検出しようとせず、プログラムの動作を決定論的にするために、デフォルト値への初期化に依存します。

デイビッドの回答に対するコメント者は、静的分析を介して未割り当てフィールドの使用を検出することが不可能な理由を尋ねています。これが、この回答で拡張したいポイントです。

最初に、ローカルまたはその他の変数については、実際にはexactly変数が割り当てられているか割り当てられていないかを判断することはできません。考慮してください:

bool x;
if (M()) x = true;
Console.WriteLine(x);

「xが割り当てられていますか?」という質問「M()はtrueを返しますか?」と同等ですここで、M()は、eleventy gajillion未満のすべての整数についてFermatの最終定理がtrueである場合にtrueを返し、それ以外の場合にfalseを返すと仮定します。 xが確実に割り当てられているかどうかを判断するために、コンパイラは基本的にFermatの最終定理の証明を作成する必要があります。コンパイラーはそれほど賢くありません。

そのため、コンパイラがローカルに対して代わりに行うことは、ローカルが明確に割り当てられていない場合にfastおよびoverestimatesのアルゴリズムを実装することです。つまり、いくつかの誤検出があり、「あなたと私はそれがわかっているのに、「このローカルが割り当てられていることを証明できない」と言っています。例えば:

bool x;
if (N() * 0 == 0) x = true;
Console.WriteLine(x);

N()が整数を返すと仮定します。あなたと私はN() * 0が0になることを知っていますが、コンパイラはそれを知りません。 (注:C#2.0コンパイラdidはそれを知っていますが、仕様がsayを知らないため、その最適化を削除しました。)

それでは、これまでのところ何を知っていますか?地元の人が正確な答えを得るのは実用的ではありませんが、割り当てられていないことを安価に過大評価し、「不明瞭なプログラムを修正する」という間違いを犯すかなり良い結果を得ることができます。それは良い。フィールドに対して同じことをしないのはなぜですか?つまり、安価に過大評価する明確な割り当てチェッカーを作成しますか?

さて、ローカルを初期化する方法はいくつありますか?メソッドのテキスト内で割り当てることができます。メソッドのテキスト内のラムダ内で割り当てることができます。そのラムダは呼び出されない可能性があるため、これらの割り当ては関係ありません。または、「out」として別のメソッドに渡すことができます。この時点で、メソッドが正常に戻ったときに割り当てられていると想定できます。これらは、ローカルが割り当てられる非常に明確なポイントであり、ローカルが宣言されているのと同じメソッドですぐにです。ローカルの明確な割り当てを決定するには、ローカル分析のみが必要です。メソッドは短くなる傾向があります(メソッドのコード行数が100万行をはるかに下回る)ため、メソッド全体の分析は非常に高速です。

では、フィールドについてはどうでしょうか?もちろん、フィールドはコンストラクターで初期化できます。またはフィールド初期化子。または、コンストラクターは、フィールドを初期化するインスタンスメソッドを呼び出すことができます。または、コンストラクターは、フィールドを初期化するvirtualメソッドを呼び出すことができます。または、コンストラクターは、フィールドを初期化するメソッド別のクラス内を呼び出すことができます。これはライブラリ内である場合があります。静的フィールドは、静的コンストラクターで初期化できます。静的フィールドはother静的コンストラクターによって初期化できます。

基本的に、フィールドの初期化子はプログラム全体の任意の場所で、内部を含むまだ記述されていないライブラリで宣言される仮想メソッドです。

// 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() { } 
}

thatエラーですか? コンパイラはどのようにそれを把握することになっていますか?唯一の方法はプログラム全体の解析の初期化静的を追跡することですすべてのフィールド on プログラムを通るすべての可能なパス実行時の仮想メソッドの選択を含むパスを含む。この問題は任意に難しいである可能性があります。数百万の制御パスのシミュレーション実行が含まれる場合があります。ローカル制御フローの分析にはマイクロ秒かかり、メソッドのサイズに依存します。 プログラムとすべてのライブラリのすべてのメソッドの複雑さに依存するため、グローバル制御フローの分析には数時間かかる場合があります。

それでは、プログラム全体を分析する必要がなく、さらに厳しく過大評価するだけの安価な分析をしてみませんか?さて、実際にコンパイルする正しいプログラムを作成するのが難しくない、動作するアルゴリズムを提案してください。設計チームはそれを検討できます。私はそのようなアルゴリズムを知りません。

現在、コメンターは「コンストラクターがすべてのフィールドを初期化することを要求する」ことを提案しています。それは悪い考えではありません。実際、C#には既に構造体用の機能がありますという悪い考えではありません。 structコンストラクターは、ctorが正常に戻るまでにすべてのフィールドを確実に割り当てるために必要です。デフォルトのコンストラクタは、すべてのフィールドをデフォルト値に初期化します。

クラスはどうですか?さて、コンストラクタがフィールドを初期化したことをどのように知っていますか? ctorは、フィールドを初期化するために仮想メソッドを呼び出すことができました。これで、以前と同じ位置に戻りました。構造体には派生クラスがありません。クラスかもしれない。抽象クラスを含むライブラリは、そのすべてのフィールドを初期化するコンストラクターを含む必要がありますか?抽象クラスは、フィールドを初期化する値をどのように知るのですか?

Johnは、フィールドが初期化される前に、アクター内のメソッドの呼び出しを単に禁止することを提案しています。要約すると、オプションは次のとおりです。

  • 一般的で安全な、頻繁に使用されるプログラミングのイディオムを違法にします。
  • おそらくそこにないバグを探すために、コンパイルに数時間かかる高価なプログラム全体の分析を行ってください。
  • デフォルト値への自動初期化に依存します。

設計チームは3番目のオプションを選択しました。

177
Eric Lippert

メソッド内で同じboolを作成すると、(クラス内ではなく)bool checkを実行すると、「unassigned local variable checkの使用」というエラーが表示されます。どうして?

コンパイラはあなたが間違いをしないようにしようとしているからです。

変数をfalseに初期化すると、この特定の実行パスの何かが変更されますか?おそらく、default(bool)がとにかく偽であることを考慮すると、おそらくあなたはawareであることを強制されています。 .NET環境では、すべての値がデフォルトに初期化されるため、「ガベージメモリ」にアクセスできません。それでも、これが参照型であり、初期化されていない(null)値をnull以外のメソッドに渡すと、実行時にNREを取得すると想像してください。コンパイラは単にそれを防止しようとしているだけで、これによりbool b = falseステートメントが発生することがあるという事実を受け入れています。

Eric Lippertがこれについて語っています ブログ投稿

多くの人が信じているように、これを違法にしたいのは、ローカル変数がゴミに初期化され、ゴミからあなたを保護したいからではありません。実際、ローカルをデフォルト値に自動的に初期化します。 (CおよびC++プログラミング言語ではサポートされていませんが、初期化されていないローカルからガベージを読み取ることができます。)むしろ、それは、そのようなコードパスの存在がおそらくバグだからです、そして私たちはあなたを品質の落とし穴に投げ込みたいです。そのバグを書くために一生懸命働く必要があります。

なぜこれがクラスフィールドに適用されないのですか?さて、線はどこかに描画する必要があり、ローカル変数の初期化は、クラスフィールドとは対照的に、診断と正しい取得がはるかに簡単です。コンパイラcouldこれを行いますが、クラス内の各フィールドが以下であるかどうかを評価するために行う必要がある可能性のあるすべてのチェック(それらの一部はクラスコード自体から独立している場合)初期化されました。私はコンパイラの設計者ではありませんが、間違いなくharderになると確信しています。多くのケースが考慮されており、timely fashionで行う必要があります同じように。設計、作成、テスト、展開する必要があるすべての機能について、これを実装することの価値は、努力する価値がなく、複雑になります。

27
Yuval Itzchakov

ローカル変数は初期化を必要とするのにフィールドは必要ないのはなぜですか?

簡単な答えは、初期化されていないローカル変数にアクセスするコードは、静的分析を使用して、信頼できる方法でコンパイラによって検出できるということです。一方、これはフィールドの場合ではありません。したがって、コンパイラは最初のケースを強制しますが、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行目はコンパイルされません。初期化されていない文字列変数を読み取ろうとしているためです。 arrayは初期化されていますが、デフォルト値のみであるため、コードの4行目は問題なくコンパイルされます。文字列のデフォルト値はnullであるため、実行時に例外が発生します。ここでStack Overflowに時間を費やしている人なら誰でも、この明示的/暗黙的な初期化の不一致が非常に多くの「「オブジェクトのインスタンスに設定されていないオブジェクト参照」エラーを取得するのはなぜですか?」質問。

25
David Arno

上記の良い答えですが、長いもの(私のように)を読むのが面倒な人のために、もっと簡単/短い答えを投稿したいと思いました。

クラス

class Foo {
    private string Boo;
    public Foo() { /** bla bla bla **/ }
    public string DoSomething() { return Boo; }
}

プロパティBooは、コンストラクターで初期化されている場合と初期化されている場合がありますnot。したがって、return Boo;を見つけたとき、初期化されたとは仮定しません。エラーを単に抑制します。

関数

public string Foo() {
   string Boo;
   return Boo; // triggers error
}

{ }文字は、コードブロックのスコープを定義します。コンパイラは、これらの{ }ブロックのブランチを調べて、ものを追跡します。 Booが初期化されていないことをeasilyで知ることができます。その後、エラーがトリガーされます。

なぜエラーが存在するのですか?

このエラーは、ソースコードを安全にするために必要なコードの行数を減らすために導入されました。エラーがなければ、上記は次のようになります。

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

10
Reactgular