开发者

How to pass around event as parameter in C#

I am writing unit tests for a multi-threading application, where I need to wait until a specific event is triggered so that I know the asynchronous operation is done. For example, when I call repository.add(something), I wait for event AfterChange before doing any assertion. So I wrote a utility function to do that:

public static void SyncAction(EventHandler event_, Action action_)
{
    var signal = new object();
    EventHandler callback = null;
    callback = new EventHandler((s, e) =>
    {
        lock (signal)
        {
          Monitor.Pulse(signal);
        }

        event_ -= callback;
    });

    event_ += callback;

    lock (signal)
    {
        action_();
        Assert.IsTrue(Monitor.Wait(signal, 10000));
    }
}

However, the compiler prevents from开发者_如何学Python passing the event out of the class. Is there a way to achieve that?

Below is the solution using reflection.

public static void SyncAction(object target_, string event_, Action action_)
{
    SyncAction(
        new List<Pair<object, string>>() { new Pair<object, string>(target_, event_) },
        action_);
}

public static void SyncAction(IEnumerable<Pair<object, string>> events_, Action action_)
{
    var events = events_
        .Select(a => new Pair<object, EventInfo>(a.First, a.First.GetType().GetEvent(a.Second)))
        .Where(a => a.Second != null);

    var count = events.Count();
    var signal = new object();
    var callback = new EventHandler((s, e) =>
    {
        lock (signal)
        {
            --count;
            Monitor.Pulse(signal);
        }
    });

    events.ForEach(a => a.Second.AddEventHandler(a.First, callback));

    lock (signal)
    {
        action_();
        while (count > 0)
        {
            Assert.IsTrue(Monitor.Wait(signal, 10000));
        }
    }
    events.ForEach(a => a.Second.RemoveEventHandler(a.First, callback));
}


The problem is that events aren't really first-class values in .NET :( (Ditto properties, methods etc.)

Reactive Extensions handles this in two ways:

  • You can provide the name of the event and the target, and it will use reflection... something like this:

    var x = Observable.FromEvent<EventArgs>(button, "Click");
    
  • You can provide a delegate for subscription and a delegate for unsubscription:

    var x = Observable.FromEvent<EventArgs>(
          handler => button.Click += handler,
          handler => button.Click -= handler);
    

(The exact methods may be slightly different, but that's the general idea.)

Of course, if you're happy to use Reactive Extensions yourself, you could use those methods and make your test use IObservable<T> :)


Based on the syntax used in Jon Skeet's answer, this could be a custom solution:

public static void AssertEventIsFired<T>(
    Action<EventHandler<T>> attach, 
    Action<EventHandler<T>> detach, 
    Action fire) where T : EventArgs
{
    AutoResetEvent fired = new AutoResetEvent(false);
    EventHandler<T> callback = new EventHandler<T>(
      (s, e) =>
      {
          detach(callback);
          fired.Set();
      });

    attach(callback);
    fire();
    Assert.IsTrue(fired.WaitOne(10000));
}

Usage:

AssertEventIsFired<EventArgs>(
    h => obj.EventFired += h,
    h => obj.EventFired -= h,
    () => obj.DoSomethingToFireEvent());
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜