2010-05-18 2 views
2

Я пишу модульные тесты для многопоточного приложения, где мне нужно подождать, пока не будет запущено определенное событие, чтобы я знал, что выполняется асинхронная операция. Например, когда я вызываю repository.add(something), я жду события AfterChange перед выполнением любого утверждения. Поэтому я написал функцию полезности для этого:Как передать событие как параметр в C#

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

ответ

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> :)

+0

Am с использованием 2-й подход, и будет пытаться на отражение. Rx еще не утвердил в моей компании, что я не могу ждать. Но определенно будет намного легче написать тестовые примеры, используя Rx. –

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()); 
+0

Я использую точно такую ​​же работу, как сейчас. Это прекрасно работает, хотя и не изящно. но это становится раздражающим, когда я потратил его на подключение нескольких событий, мне нужны 2 списка для прикрепления и отсоединения. –

Смежные вопросы