10

私はランバ式を使用してイベントを強く型付けされた方法で接続できるようにすることを検討していましたが、途中でリスナーを使用します。たとえば、次のクラスが与えられました

class Producer
{
    public event EventHandler MyEvent;
}

class Consumer
{
    public void MyHandler(object sender, EventArgs e) { /* ... */ }
}

class Listener
{
    public static void WireUp<TProducer, TConsumer>(
        Expression<Action<TProducer, TConsumer>> expr) { /* ... */ }
}

イベントは次のように結び付けられます。

Listener.WireUp<Producer, Consumer>((p, c) => p.MyEvent += c.MyHandler);

ただし、これによりコンパイラエラーが発生します。

CS0832: 式ツリーに代入演算子が含まれていない可能性があります

特に式ツリーに代入を含めることができない理由についての説明を読んだ後は特にそうです。ただし、C# の構文にもかかわらず+=、これは代入ではなく、Producer::add_MyEventメソッドの呼び出しです。これは、イベントを通常どおり接続した場合に生成される CIL からわかるように、次のとおりです。

L_0001: newobj instance void LambdaEvents.Producer::.ctor()
L_0007: newobj instance void LambdaEvents.Consumer::.ctor()
L_000f: ldftn instance void LambdaEvents.Consumer::MyHandler(object, class [mscorlib]System.EventArgs)
L_0015: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int)
L_001a: callvirt instance void LambdaEvents.Producer::add_MyEvent(class [mscorlib]System.EventHandler)

したがって、割り当てが許可されていないことを訴えているため、これはコンパイラのバグのように見えますが、割り当ては行われておらず、メソッド呼び出しだけです。それとも私は何かを逃していますか...?

編集:

「この動作はコンパイラのバグですか?」という質問があることに注意してください。私が何を求めているのか明確でなかったら申し訳ありません。

編集 2

Inferis の回答を読んだ後、彼は「その時点で += は代入と見なされます」と述べていますが、これはある程度の意味があります。

ただし、明示的なメソッド呼び出しフォームを書くことは許可されていません。

Listener.WireUp<Producer, Consumer>(
    (p, c) => p.add_MyEvent(new EventHandler(c.MyHandler)));

与えます:

CS0571: 'Producer.MyEvent.add': 演算子またはアクセサーを明示的に呼び出すことはできません

+=したがって、問題は、C# イベントのコンテキストで実際に何を意味するのかということになると思います。「このイベントの add メソッドを呼び出す」という意味ですか、それとも「まだ定義されていない方法でこのイベントに追加する」という意味ですか。前者の場合、これはコンパイラのバグのように見えますが、後者の場合、やや直感的ではありませんが、間違いなくバグではありません。考え?

4

5 に答える 5

5

仕様のセクション 7.16.3 では、+= および -= 演算子は「イベント割り当て」と呼ばれ、確かに割り当て演算子のように聞こえます。セクション 7.16 (「代入演算子」) 内にあるという事実は、かなり大きなヒントです:) その観点から、コンパイラエラーは理にかなっています。

ただし、式ツリーがラムダ式によって与えられる機能を表すことは完全に可能であるため、過度に制限的であることに同意します。

言語設計者は、このような状況を犠牲にして、「少し制限的ですが、演算子の記述においてより一貫した」アプローチを採用したのではないかと思います。

于 2009-02-19T12:21:13.383 に答える
1

+= は、それが何をするかに関係なく、代入です (例: イベントの追加)。パーサーの観点からは、これはまだ割り当てです。

試しましたか

Listener.WireUp<Producer, Consumer>((p, c) => { p.MyEvent += c.MyHandler; } );
于 2009-02-19T12:03:11.930 に答える
1

Expression クラスを使用する理由は何ですか? Expression<Action<TProducer, TConsumer>>コードを単純に変更するとAction<TProducer, TConsumer>、すべてが必要に応じて機能するはずです。ここで行っているのは、コンパイラにラムダ式をデリゲートではなく式ツリーとして扱うように強制することであり、式ツリーには実際にそのような代入を含めることはできません (+= 演算子を使用しているため、代入として扱われます) )。現在、ラムダ式はどちらの形式にも変換できます ([MSDN][1] に記載されています)。デリゲートを使用するだけで (これが Action クラスのすべてです)、そのような「割り当て」は完全に有効です。ここで問題を誤解している可能性があります (おそらく、式ツリーを使用する必要がある特定の理由があるのでしょうか?) が、解決策は幸いなことにこのように単純なようです!

編集:そうですね、コメントからあなたの問題を少しよく理解しています。p.MyEvent と c.MyHandler を引数として WireUp メソッドに渡し、WireUp メソッド内にイベント ハンドラーをアタッチできない理由はありますか (これは設計の観点からも優れているように思えます)...式ツリーの必要性を排除しないのですか? 式ツリーはデリゲートに比べてかなり遅くなる傾向があるため、とにかく式ツリーを避けるのが最善だと思います。

于 2009-02-19T12:10:13.317 に答える
1

実際、その時点でコンパイラに関する限り、それ代入です。+= 演算子はオーバーロードされていますが、コンパイラはその時点でそれを気にしません。結局のところ、ラムダ (ある時点で実際のコードにコンパイルされる) を介して式を生成しており、実際のコードは生成していません。

したがって、コンパイラが行うことはc.MyHandler、 の現在の値に追加する式を作成しp.MyEvent、変更された値を に戻すことp.MyEventです。そして、最終的にはそうではなくても、実際には課題をやっているのです。

WireUp メソッドに Action だけでなく式を使用させたい理由はありますか?

于 2009-02-19T12:11:26.560 に答える
0

問題は、オブジェクトとは別にExpression<TDelegate>、式ツリーがコンパイラーの観点から静的に型付けされていないことだと思います。MethodCallExpression友人は静的型付け情報を公開しません。

コンパイラは式のすべての型を認識していますが、この情報はラムダ式を式ツリーに変換するときに破棄されます。(式ツリー用に生成されたコードを見てください)

それでも、これをマイクロソフトに提出することを検討します。

于 2009-02-19T12:11:11.163 に答える