11

次のソースを検討してください。

static void Main(string[] args)
{
    bool test;

    Action lambda = () => { test = true; };
    lambda();

    if (test)
        Console.WriteLine("Ok.");
}

コンパイルする必要がありますよね?まあ、そうではありません。私の質問は、C#標準に従って、このコードをコンパイルする必要があるのか​​、それともコンパイラのバグなのかということです。


エラーメッセージ:

Use of unassigned local variable 'test'

注:私はエラーを修正する方法を知っています、そして私は部分的に知っています、なぜそれが起こるのですか?ただし、ローカル変数は無条件に割り当てられ、コンパイラはそれに気付くはずですが、気付かないでしょう。なんでだろうか。


回答へのコメント:C#では、割り当てられていない変数を宣言できます。これは、実際には非常に便利です。

bool cond1, cond2;
if (someConditions)
{
    cond1 = someOtherConditions1;
    cond2 = someOtherConditions2;
}
else
{
    cond1 = someOtherConditions3;
    cond2 = someOtherConditions4;
}

コンパイラはこのコードを適切にコンパイルします。変数を割り当てないままにしておくと、実際にはコードが少し良くなると思います。理由は次のとおりです。

  • これは、値が後で割り当てられることを読者に伝えます(ほとんどの場合、次の条件ステートメントで)
  • 内部条件のすべてのブランチに変数を割り当てるようにプログラマーに強制します(最初からこのコードの目的であった場合)。これは、ブランチの1つが変数のいずれかを割り当てない場合、コンパイラーがコードのコンパイルを拒否するためです。

余白について:それはさらに興味深いことです。C++の同じ例を考えてみましょう。

int main(int argc, char * argv[])
{
    bool test;

    /* Comment or un-comment this block
    auto lambda = [&]() { test = true; };
    lambda();
    */

    if (test)
        printf("Ok.");

    return 0;
}

ブロックをコメントアウトすると、コンパイルは警告で終了します。

main.cpp(12): warning C4700: uninitialized local variable 'test' used

ただし、コメントを削除すると、コンパイラは警告を発しません。結局、変数が設定されているかどうかを判断できるように思えます。

4

4 に答える 4

17

私の質問は、C#標準に従って、このコードをコンパイルする必要があるのか​​、それともコンパイラのバグなのかということです。

これはバグではありません。

C#言語仕様(4.0)のセクション5.3.3.29は、ラムダ式を含む無名関数に関する明確な割り当てルールの概要を示しています。ここに投稿します。

5.3.3.29無名関数

本体(ブロックまたは式のいずれか)本体を持つラムダ式またはanonymous-method-expression exprの場合:

  • bodyの前の外部変数vの明確な割り当て状態は、exprの前のvの状態と同じです。つまり、外部変数の明確な割り当て状態は、無名関数のコンテキストから継承されます。

  • expr後の外部変数vの明確な割り当て状態は、expr前のvの状態と同じです。

delegate bool Filter(int i);

void F() {
    int max;

    // Error, max is not definitely assigned    
    Filter f = (int n) => n < max;

    max = 5;    
    DoWork(f); 
}

匿名関数が宣言されている場所にmaxが明確に割り当てられていないため、コンパイル時エラーが生成されます。例

delegate void D();

void F() {    
    int n;    
    D d = () => { n = 1; };

    d();

    // Error, n is not definitely assigned
    Console.WriteLine(n); 
}

また、無名関数でのnへの割り当ては、無名関数外でのnの明確な割り当て状態に影響を与えないため、コンパイル時エラーが発生します。

これが特定の例にどのように適用されるかを確認できます。testラムダ式の宣言の前に、変数が具体的に割り当てられることはありません。ラムダ式の実行前に特別に割り当てられることはありません。また、ラムダ式の実行が完了した後は、特に割り当てられません。if原則として、コンパイラーは、ステートメントで読み取られる時点で変数が確実に割り当てられているとは見なしません。

理由については、私が読んだことと、リンクを作成できないために覚えていることだけを繰り返すことができますが、これは目に見える些細なケースですが、C#はこれを試みません。 、このタイプの分析は自明ではなく、実際に停止性問題の解決につながる可能性がある場合がはるかに多いです。したがって、C#は「シンプルに保つ」ため、より簡単に適用可能で解決可能なルールに従ってプレイする必要があります。

于 2013-01-08T21:47:47.323 に答える
5

割り当てられていない変数を使用しています。変数が実際に割り当てられている場合でも、コンパイラーには、投稿したコードから変数を推測する方法がありません。

とにかく宣言されたときにすべてのローカル変数を初期化する必要があるので、これは興味深いですが、それでも誤りです。

于 2013-01-08T21:22:59.420 に答える
2

コンパイラがメソッドの制御フロー分析を実行して、変数が確実に割り当てられているかどうかを判断する場合、コンパイラは現在のメソッドのスコープ内でのみ検索します。Eric Lippertは、このブログ投稿でこれについて説明しています。理論的には、コンパイラが「現在のメソッド」内から呼び出されたメソッドを分析して、変数が確実に割り当てられているかどうかを推論することができます。

前に述べたように、手続き間分析を行うことはできますが、実際には、それは本当に厄介なものになります。グループ内の別のメソッドをすべて無限ループに入れたり、スローしたり、呼び出したりする、相互に再帰的な100個のメソッドを想像してみてください。呼び出しの複雑なトポロジーから到達可能性を論理的に推測できるコンパイラーを設計することは実行可能ですが、多くの作業が必要になる可能性があります。また、手続き間分析は、手続きのソースコードがある場合にのみ機能します。これらのメソッドの1つがアセンブリ内にあり、メタデータだけを処理する必要がある場合はどうなりますか?

コード例は、実際には単一のメソッドではないことに注意してください。匿名メソッドは別のクラスにリファクタリングされ、そのインスタンスが作成され、定義に似たメソッドが呼び出されます。delegateさらに、コンパイラーは、クラスの定義と、Action提供したメソッドが実際に実行された理由の定義を分析する必要があります。

したがって、コンパイラがそのコンテキストで変数に到達可能であることを知ることは理論的な可能性の範囲内ですが、コンパイラの作成者は、コンパイラの記述が複雑であるため、また(潜在的に重要な)増加するため、意図的に両方を選択しません。やがてプログラムのコンパイルにかかるでしょう。

于 2013-01-08T22:02:51.890 に答える
1

ECMA標準セクション8.3変数とパラメーターからの抜粋:

変数は、その値を取得する前に割り当てる必要があります。例

class Test
{
    static void Main() {
    int a;
    int b = 1;
    int c = a + b; // error, a not yet assigned

    }
}

値が割り当てられる前に変数aを使用しようとするため、コンパイル時エラーが発生します。明確な割り当てを管理する規則は、§12.3で定義されています。

したがって、使用する前に変数を割り当てる必要があると記載されています。そうしないと、コンパイラエラーが発生します。デリゲートを作成して呼び出すため、デリゲート呼び出しに含まれるメソッドは技術的には不明です。したがって、コンパイラはそれを理解することはありません。実際のメソッドではなく、呼び出されているのはDelegateのInvokeメソッドであることを忘れないでください。

C#のECMA標準

于 2013-01-08T21:25:31.503 に答える