3

私はこれがここでこの質問のわずかに複製であることを知っています:イベントのブロックと待機

しかし、私はEventWaiterを作成している途中で、問題が発生しました。これが私が取り組んできたものの(主に)簡略化されたバージョンです:

public class EventWaiter
{
    private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
    private EventInfo _event = null;
    private object _eventContainer = null;

    public EventWaiter(object eventContainer, string eventName)
    {
        _eventContainer = eventContainer;
        _event = eventContainer.GetType().GetEvent(eventName);
    }
    public void WaitForEvent()
    {
        MethodInfo method = this.GetType().GetMethod("DynamicCaller");
        Delegate handler = Delegate.CreateDelegate(this._event.EventHandlerType, this, method);

        _event.AddEventHandler(_eventContainer, handler);

        _autoResetEvent.WaitOne();

        _event.RemoveEventHandler(_eventContainer, _handler);

    }
    public void DynamicCaller(/* insert magic here */)
    {
        _autoResetEvent.Set();
    }
}

使用法は単純に次のようになります。

EventWaiter ew = new EventWaiter(someClass, "someEvent");
ew.WaitForEvent();

基本的に何が起こっているのかというと、DynamicCallerこのイベントのハンドラーとしてvoidを登録することです。問題は、イベントの署名が異なることです。使用するデリゲートに関係なく、イベントを処理できるようにしたいのです。

this._event.EventHandlerTypeを使用してデリゲートのタイプを取得できますが、デリゲートが何であっても、完全に再利用可能なクラスを作成するためにどのように使用できますか?DynamicCallerパラメーターがイベントデリゲートパラメーターと完全に同じでない場合、例外が発生します。

ちなみに、私はフレームワークのコードをたくさん調べました。その一部にアクセスできれば、これは簡単だと思います。私が必要とするクラスの多くがすべてフレームワークの内部にあるのは残念です。

4

4 に答える 4

2

推奨されるパターンを尊重するすべてのイベントには、オブジェクト型のパラメーターと から派生した型のパラメーターがあるため、EventArgsこれらすべてのイベントをこの署名で処理できるはずです。

void DynamicCaller(object sender, EventArgs e)

もちろん、非標準のイベント署名では機能しません...


編集:動的に生成されたハンドラーの例を次に示します。

public class EventWaiter
{
    private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
    private EventInfo _event = null;
    private object _eventContainer = null;

    public EventWaiter(object eventContainer, string eventName)
    {
        _eventContainer = eventContainer;
        _event = eventContainer.GetType().GetEvent(eventName);
    }
    public void WaitForEvent()
    {
        Delegate handler = CreateHandler();

        _event.AddEventHandler(_eventContainer, handler);

        _autoResetEvent.WaitOne();

        _event.RemoveEventHandler(_eventContainer, handler);

    }

    private Delegate CreateHandler()
    {
        var invokeMethod = _event.EventHandlerType.GetMethod("Invoke");
        var invokeParameters = invokeMethod.GetParameters();
        var handlerParameters = invokeParameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
        var body = Expression.Call(Expression.Constant(_autoResetEvent), "Set", null);
        var handlerExpression = Expression.Lambda(_event.EventHandlerType, body, handlerParameters);
        return handlerExpression.Compile();
    }
}

編集:SLaksは私より速かった;)

于 2012-08-12T17:54:08.313 に答える
1

式ツリーを使用して、コールバックを呼び出す任意のパラメーター セットでメソッドをコンパイルする必要があります。

Expression.Lambda(
    _event.EventHandlerType,

    Expression.Call(Exrpession.Constant(_autoResetEvent), 
                    typeof(AutoResetEvent).GetMethod("Set")),

    _event.EventHandlerType.GetMethod("Invoke")
                           .GetParameters()
                           .Select(p => Expression.Parameter(p.ParameterType))
).Compile();

ジェネリックと式ツリーを使用して、システムをタイプ セーフにすることができることに注意してください。

 new EventWaiter(_ => someObject.SomeEvent += _)

は通常の (ただし_短い) パラメータ名です。

于 2012-08-12T17:58:14.600 に答える
0

ここでの解決策は良いですが、私にとって、文字列を使用すると、リフレクションにはコードの臭いが少しあるので、汎用バージョンを選択します。

public class EventWaiter
{
    public enum Mode
    {
        Wait,
        Detach
    }

    public static Func<Mode, TEventArgs> Create<TDelegate, TEventArgs>(
        Func<Action<object, TEventArgs>, TDelegate> converter,
        Action<TDelegate> addHandler,
        Action<TDelegate> removeHandler
        )
    {
        AutoResetEvent semaphore = new AutoResetEvent(false);
        TEventArgs args = default(TEventArgs);

        TDelegate handler = converter((s, e) => {  args = e; semaphore.Set(); });

        addHandler(handler);

        return mode =>
        {
            if (mode == Mode.Wait)
            {
                semaphore.WaitOne(); 
                return args;
            }
            else
            {
                removeHandler(handler);
                return default(TEventArgs);
            }
        };
    }

使用法:

        var evt = 
        EventWaiter.Create<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>
            (handler => (s, e) => handler(s, e), 
            h => port.DataReceived += h,
            h => port.DataReceived -= h);

        var firstArgument = evt(EventWaiter.Mode.Wait); //Wait for first event
        var secondArgument = evt(EventWaiter.Mode.Wait); //Wait for second event

        evt(EventWaiter.Mode.Detach); //Dispose
于 2012-08-12T18:46:26.003 に答える
0

あなたはあなたがやりたいことをすることができますTaskCompletionSource

  TaskCompletionSource<string> tcs = 
    new TaskCompletionSource<string>();

  WebClient client = new WebClient();

  client.DownloadStringCompleted += (sender, args) => {
    if (args.Error != null) tcs.SetException(args.Error);
    else if (args.Cancelled) tcs.SetCanceled();
    else tcs.SetResult(args.Result);
  };

  client.DownloadStringAsync(address);

  tcs.Task.Wait(); // WaitForEvent
于 2012-08-12T18:06:35.433 に答える