44

以下のコードを検討してください。完全に有効な 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
                            };
    }
}

'Delegate 'Action' does not take 0 arguments.' というコンパイラ エラーが発生します。(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# コンパイラのバグに遭遇しましたか?

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

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

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

4

3 に答える 3

67

最終更新:

バグはC#5で修正されました。ご不便をおかけして申し訳ありません。また、レポートに感謝します。


元の分析:

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

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

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

報告ありがとうございます!

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

すぐにわかります。

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

とにかく、魅力的なバグ、レポートをありがとう。明らかに、回避策があります。ここからQAで追跡し、C#5で修正しようとします(すでにベータ版であるService Pack 1のウィンドウを見逃しました)。

于 2010-12-17T02:14:00.593 に答える
24

これはおそらく型推論の問題であり、コンパイラは代わりに推論aするようです(署名に適合すると思われるかもしれません) 。タイプを明示的に指定してみてください。Action<T>ActionaActionSurrogateAction<Action>>a

    ActionSurrogate b = (Action a) =>
                        {
                            a();
                        };

そうでない場合は、プロジェクトの周囲で、Action1つのパラメーターを使用する自己定義のデリゲートがないかどうかを確認する場合があります。

于 2010-12-17T01:09:52.400 に答える
2
    public static void ThisWontCompile()
        {
            ActionSurrogate b = (Action a) =>
            {
                a();
            };


        }

これはコンパイルされます。パラメータなしで Action デリゲートを見つけることができないコンパイラの不具合。そのため、エラーが発生しています。

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>();
于 2010-12-17T01:45:42.067 に答える