如何在C#中传递事件作为参数

时间:2010-05-18 12:40:58

标签: c# .net multithreading

我正在为多线程应用程序编写单元测试,我需要等到特定事件被触发才能知道异步操作已经完成。例如,当我调用repository.add(something)时,我会在执行任何断言之前等待事件AfterChange。所以我写了一个实用函数来做到这一点:

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));
    }
}

但是,编译器阻止将事件传递出类。有没有办法实现这个目标?

以下是使用反射的解决方案。

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));
}

2 个答案:

答案 0 :(得分:4)

问题是事件在.NET中不是真正的第一类值:((同上属性,方法等)

Reactive Extensions以两种方式解决这个问题:

  • 您可以提供事件的名称和目标,它将使用反射......如下所示:

    var x = Observable.FromEvent<EventArgs>(button, "Click");
    
  • 您可以提供订阅的委托和取消订阅的委托:

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

(确切的方法可能略有不同,但这是一般的想法。)

当然,如果您乐意自己使用Reactive Extensions,可以使用这些方法并使用IObservable<T>进行测试:)

答案 1 :(得分:3)

根据Jon Skeet's answer中使用的语法,这可能是一个自定义解决方案:

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));
}

用法:

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