web-dev-qa-db-ja.com

「デリゲート 'System.Action'は引数を取りません。」これはC#コンパイラのバグですか(lambdas + 2つのプロジェクト)?

以下のコードを検討してください。完全に有効なC#コードのように見えますか?

//Project B
using System;
public delegate void ActionSurrogate(Action addEvent);
//public delegate void ActionSurrogate2();
// Using ActionSurrogate2 instead of System.Action results in the same error
// Using a dummy parameter (Action<double, int>) results in the same error

// Project A
public static class Class1 {
    public static void ThisWontCompile() {
        ActionSurrogate b = (a) =>
                            {
                                a(); // Error given here
                            };
    }
}

コンパイラエラー「デリゲート「アクション」は0引数を取らない」が表示されます。 (Microsoft)C#4.0コンパイラを使用して、指定された位置に配置します。このエラーが発生するには、別のプロジェクトでActionSurrogateを宣言する必要があることに注意してください。

もっと面白くなります:

// Project A, File 1
public static class Class1 {
    public static void ThisWontCompile() {
        ActionSurrogate b = (a) => { a(); /* Error given here */ };
        ActionSurrogate c = (a) => { a(); /* Error given here too */ };
        Action d = () => { };
        ActionSurrogate c = (a) => { a(); /* No error is given here */ };
    }
}

ここでC#コンパイラのバグに遭遇しましたか?

これはラムダをたくさん使用するのが好きで、将来使用するためにデータ構造ライブラリを作成しようとしている人にとってはかなり厄介なバグであることに注意してください...(私)

編集:誤ったケースを削除しました。

これを実現するために、元のプロジェクトをコピーして最小限に減らしました。これが文字通り、私の新しいプロジェクトのすべてのコードです。

42
Alex ten Brink

最終更新:

このバグはC#5で修正されました。ご不便をおかけして申し訳ありません。また、レポートをありがとうございます。


元の分析:

コマンドラインコンパイラで問題を再現できます。確かにバグのようです。それはおそらく私のせいです。申し訳ありません。 (ラムダからデリゲートへの変換チェックコードはすべて記述しました。)

今は喫茶店にいるので、ここからコンパイラのソースにアクセスできません。明日、デバッグビルドでこれを再現する時間を見つけて、何が起こっているのか理解できるかどうかを確認します。時間がなければ、クリスマスが終わるまで不在です。

Actionタイプの変数を導入すると問題が消えるという観察結果は非常に興味深いものです。コンパイラーは、パフォーマンス上の理由と言語仕様で必要な分析の両方のために、多くのキャッシュを維持します。特にラムダとローカル変数には、多くの複雑なキャッシュロジックがあります。ここで、一部のキャッシュが初期化されているか、ここで間違って入力されていること、およびローカル変数を使用すると、キャッシュの正しい値が入力されることを、1ドルも賭けてもかまいません。

レポートをありがとう!

更新:私は今バスに乗っていて、ちょうど私のところに来ました。私は何が悪いのか正確に知っていると思います。コンパイラーはlazyであり、特にメタデータからの型を処理する場合はそうです。その理由は、参照されるアセンブリに数十万のタイプが存在する可能性があり、すべてのタイプに関する情報をロードする必要がないためです。あなたはおそらくそれらの1%よりはるかに少ない量を使用するでしょうので、多くの時間と決して使用するつもりのないものをロードしているメモリを浪費しないようにしましょう。実際、怠惰はそれよりも深くなります。タイプは、使用される前にいくつかの「ステージ」を通過します。最初にその名前が判明し、次にそのベースタイプ、次にそのベースタイプ階層が十分に根拠があるかどうか(非循環など)、次にその型パラメーター制約、次にそのメンバー、次にメンバーが十分に根拠があるかどうか(オーバーライドによって何かがオーバーライドされます)同じシグネチャの、など)。変換ロジックが、「すべてのデリゲートパラメータの型がを持っていることを確認してください」というメソッドの呼び出しに失敗していると思います。メンバーは既知」、互換性のためにデリゲート呼び出しの署名をチェックする前。しかし、おそらくローカル変数を作成するコードはそれを行います。変換チェック中に、コンパイラに関する限り、アクションタイプには呼び出しメソッドさえないかもしれません。

すぐにわかります。

更新:今朝、私の超能力は強力です。オーバーロードの解決で、引数がゼロのデリゲート型の「Invoke」メソッドがあるかどうかを判断しようとすると、zero Invokeメソッドを見つけて選択できます。オーバーロードの解決を行う前に、デリゲート型メタデータが完全に読み込まれていることを確認する必要があります。これがこれほど長い間見過ごされてきたことはどれほど奇妙なことでしょう。 C#3.0で再現します。もちろん、ラムダがなかったからといって、C#2.0では再現されません。 C#2.0の匿名メソッドでは、タイプを明示的に指定する必要があります。これにより、ローカルが作成され、メタデータがロードされることがわかっています。しかし、バグの根本的な原因-オーバーロードの解決によって呼び出しのメタデータのロードが強制されない-は、C#1.0に戻ると思います。

とにかく、興味深いバグ、報告ありがとうございます。明らかに、回避策があります。ここからQAで追跡し、C#5の修正を試みます( Service Pack 1は既にベータ版です のウィンドウを見逃しています)。

67
Eric Lippert
    public static void ThisWontCompile()
        {
            ActionSurrogate b = (Action a) =>
            {
                a();
            };


        }

これはコンパイルされます。コンパイラーの不具合により、パラメーターなしでアクションデリゲートを見つけることができません。そのため、エラーが発生します。

public delegate void Action();
public delegate void Action<T>();
public delegate void Action<T1,T2>();
public delegate void Action<T1,T2,T3>();
public delegate void Action<T1,T2,T3,T4>();
2
Amit Bagga