Reactive Extensions очень мощный, но мне пришлось свернуть свой собственный для проекта.Вот оно:
using System.Timers;
public static class EventExtensions {
static Dictionary<EventHandler, Timer> _debounceTimers;
public static EventHandler Debounce(TimeSpan interval, Action<object, EventArgs> action) {
object lastSender = null;
EventArgs lastArgs = null;
var timer = new Timer();
if (_debounceTimers == null)
_debounceTimers = new Dictionary<EventHandler, Timer>();
timer.Interval = interval.TotalMilliseconds;
timer.Elapsed += (sender, args) => {
timer.Enabled = false;
timer.Stop();
action(lastSender, lastArgs);
};
EventHandler handler = (sender, args) => {
lastSender = sender;
lastArgs = args;
timer.Stop();
timer.Start();
timer.Enabled = true;
};
_debounceTimers.Add(handler, timer);
return handler;
}
public static void DisposeDebouncers(this EventHandler handler) {
handler.ThrowIfNull("handler");
if (_debounceTimers.ContainsKey(handler)) {
var timer = _debounceTimers[handler];
if (timer != null) {
timer.Stop();
timer.Enabled = false;
timer.Dispose();
}
}
}
}
Test
using Xunit;
public class EventExtensionsTest {
event EventHandler FrequentEvent;
[Fact]
void DebounceTest() {
var counter = 0;
var span = TimeSpan.FromSeconds(1);
var handler = EventExtensions.Debounce(span, (sender, e) => counter++);
FrequentEvent += handler;
FrequentEvent(this, null);
FrequentEvent(this, null);
FrequentEvent(this, null);
Thread.Sleep(2000);
Assert.Equal(1, counter);
FrequentEvent(this, null);
FrequentEvent(this, null);
FrequentEvent(this, null);
FrequentEvent(this, null);
FrequentEvent(this, null);
Thread.Sleep(2000);
Assert.Equal(2, counter);
FrequentEvent.DisposeDebouncers();
FrequentEvent -= handler;
}
}
object.ThrowIfNull()
- это метод расширения, который выдает ArgumentNullException, если объект имеет значение null.Я уверен, что есть непокрытые крайние случаи.