13

これは不可能だと確信していますが、それでも質問します。

イベントへのシングルショット サブスクリプションを作成するために、私はよくこの (自作の) パターンを使用していることに気づきます。

EventHandler handler=null;
handler = (sender, e) =>
{
    SomeEvent -= handler;
    Initialize();
};
SomeEvent += handler;

これは非常に多くのボイラープレートであり、Resharper が変更されたクロージャーについて不満を言う原因にもなります。このパターンを拡張メソッドなどに変える方法はありますか? それを行うより良い方法は?

理想的には、次のようなものが欲しいです:

SomeEvent.OneShot(handler)
4

3 に答える 3

4

拡張メソッドにリファクタリングするのは簡単ではありません。C# でイベントを参照できる唯一の方法は、イベントをサブスクライブ ( +=) またはサブスクライブ解除 ( -=) することです (現在のクラスで宣言されている場合を除く)。

Reactive Extensions と同じアプローチを使用できますObservable.FromEvent。2 つのデリゲートを使用して、イベントをサブスクライブし、サブスクライブを解除します。したがって、次のようなことができます。

public static class EventHelper
{
    public static void SubscribeOneShot(
        Action<EventHandler> subscribe,
        Action<EventHandler> unsubscribe,
        EventHandler handler)
    {
        EventHandler actualHandler = null;
        actualHandler = (sender, e) =>
        {
            unsubscribe(actualHandler);
            handler(sender, e);
        };
        subscribe(actualHandler);
    }
}

...

Foo f = new Foo();
EventHelper.SubscribeOneShot(
    handler => f.Bar += handler,
    handler => f.Bar -= handler,
    (sender, e) => { /* whatever */ });
于 2011-04-11T15:57:19.720 に答える
1

次のコードは私のために働きます。文字列を介してイベントを指定する必要があるのは完璧ではありませんが、それを解決する方法はわかりません。現在のC#バージョンでは不可能だと思います。

using System;
using System.Reflection;

namespace TestProject
{
    public delegate void MyEventHandler(object sender, EventArgs e);

    public class MyClass
    {
        public event MyEventHandler MyEvent;

        public void TriggerMyEvent()
        {
            if (MyEvent != null)
            {
                MyEvent(null, null);
            }
            else
            {
                Console.WriteLine("No event handler registered.");
            }
        }
    }

    public static class MyExt
    {
        public static void OneShot<TA>(this TA instance, string eventName, MyEventHandler handler)
        {
            EventInfo i = typeof (TA).GetEvent(eventName);
            MyEventHandler newHandler = null;
            newHandler = (sender, e) =>
                             {
                                 handler(sender, e);
                                 i.RemoveEventHandler(instance, newHandler);
                             };
            i.AddEventHandler(instance, newHandler);
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            MyClass c = new MyClass();
            c.OneShot("MyEvent",(sender,e) => Console.WriteLine("Handler executed."));
            c.TriggerMyEvent();
            c.TriggerMyEvent();
        }
    }
}
于 2011-04-11T21:22:48.457 に答える
1

呼び出しリストにアクセスできるように「カスタム」イベントを使用し、Interlocked.Exchange を使用して呼び出しリストを同時に読み取り、クリアすることでイベントを発生させることをお勧めします。必要に応じて、単純なリンク リスト スタックを使用して、イベントのサブスクリプション/サブスクリプション解除/発生をスレッドセーフな方法で行うことができます。イベントが発生すると、コードは Interlocked.Exchange の後で、スタック アイテムの順序を逆にすることができます。unsubscribe メソッドについては、invocation-list 項目内で単純にフラグを設定することをお勧めします。これにより、イベントが発生することなくイベントのサブスクライブとサブスクライブ解除が繰り返されると、理論的にはメモリ リークが発生する可能性がありますが、非常に簡単なスレッド セーフなサブスクライブ解除方法になります。メモリリークを回避したい場合は、リストにまだ登録されていないイベントの数を数えることができます。新しいイベントを追加しようとしたときに、リストに登録されていないイベントが多すぎる場合、add メソッドはリストを調べてそれらを削除できます。完全にロックのないスレッドセーフなコードでも機能しますが、より複雑になります。

于 2011-08-27T15:53:52.773 に答える