How to pass an event as a parameter in C #
I am writing unit tests for a multi-threaded application where I need to wait until a certain event is fired so that I know that an asynchronous operation is in progress. For example, when I call repository.add(something)
, I wait for the AfterChange event before executing any statement. So I wrote a utility function for this:
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 the event from being passed from the class. Is there a way to achieve this?
Below is a 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));
}
a source to share
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 an event name and target and it will use reflection ... something like this:
var x = Observable.FromEvent<EventArgs>(button, "Click");
-
You can provide a delegate to subscribe and a delegate to unsubscribe:
var x = Observable.FromEvent<EventArgs>( handler => button.Click += handler, handler => button.Click -= handler);
(The exact methods may differ slightly, but the general idea is.)
Of course, if you are happy with using Reactive Extensions yourself, you can use these methods and use your test IObservable<T>
:)
a source to share
Based on the syntax used in Jon Skeet's answer , this could be a customized 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));
}
Using:
AssertEventIsFired<EventArgs>(
h => obj.EventFired += h,
h => obj.EventFired -= h,
() => obj.DoSomethingToFireEvent());
a source to share