Я хочу использовать перехватчик Castle DynamicProxy для отмены уведомлений об изменениях свойств. Это работает, вроде. Событие изменения перехватывается, но вместо того, чтобы генерировать событие один раз, оно вращается и генерирует событие снова и снова с интервалом в одну секунду.
Оно никогда не выдает «OnPropertyChanged: Name». invocation.Proceed()
похоже, не работает перехваченный метод? Почему он постоянно вызывает метод debouncer ? Как заставить его вызывать перехваченный метод , один раз?
Чтобы самостоятельно проверить этот код, создайте новый. Net Core 3 проект и добавьте ссылку на Castle.Core 4.4 +0,0. Или используйте это DotNetFiddle
Ожидаемый результат:
Создание inteceptor
Экземпляр не существует в WeakDebouncers. Добавление.
Свойство не существует в экземплярах debouncers. Добавление.
Создание debouncer
Активизирующее действие
Отклонено: User.OnPropertyChanged (Имя)
Фактический вывод:
Создание inteceptor
Экземпляр в WeakDebouncers не существует. Добавление.
Свойство не существует в экземплярах debouncers. Добавление.
Создание debouncer
Вызов действия
Debounce: User.OnPropertyChanged (Name)
Экземпляр существует в WeakDebouncers
Свойство существует в экземплярах debouncers
Активизирующее действие
Debounce: User.OnPropertyChanged (Name)
Экземпляр существует в WeakDebouncers
Свойство существует в экземплярах debouncers
Invoking action
Debounce: User.OnPropertyChanged (Name)
internal static class Program
{
private static int Main()
{
var u = new User();
var interceptor = new PropertyChangedDebounceInterceptor();
var proxy = (User)new ProxyGenerator().CreateClassProxyWithTarget(
typeof(User),
u,
interceptor);
proxy.Name = "foo";
Console.ReadLine();
return 0;
}
}
Перехватчик:
public class PropertyChangedDebounceInterceptor : IInterceptor
{
private static readonly ConditionalWeakTable<object, Dictionary<string, Debouncer>> WeakDebouncers
= new ConditionalWeakTable<object, Dictionary<string, Debouncer>>();
public PropertyChangedDebounceInterceptor()
{
Console.WriteLine("Creating interceptor");
}
public void Intercept(IInvocation invocation)
{
var propertyName = (string)invocation.Arguments[0] ?? "";
var debouncer = GetDebouncer(
invocation.GetConcreteMethodInvocationTarget(),
propertyName);
debouncer.Debouce(() =>
{
Console.WriteLine($"Debounced: {invocation.TargetType.Name}.OnPropertyChanged({propertyName})");
invocation.Proceed();
});
}
private Debouncer GetDebouncer(object obj, string propertyName)
{
Dictionary<string, Debouncer> dict;
lock (WeakDebouncers)
{
if (!WeakDebouncers.TryGetValue(obj, out dict))
{
Console.WriteLine("Instance does not exist in WeakDebouncers. Adding.");
WeakDebouncers.Add(obj, dict = new Dictionary<string, Debouncer>());
}
else
{
Console.WriteLine("Instance exists in WeakDebouncers");
}
}
lock (dict)
{
if (!dict.TryGetValue(propertyName, out var debouncer))
{
Console.WriteLine("Property does not exist in instance debouncers. Adding.");
dict.Add(propertyName, debouncer = new Debouncer(TimeSpan.FromSeconds(1)));
}
else
{
Console.WriteLine("Property exists in instance debouncers");
}
return debouncer;
}
}
}
Debouncer: На основе: https://gist.github.com/cocowalla/5d181b82b9a986c6761585000901d1b8
public class Debouncer : IDisposable
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly TimeSpan _waitTime;
private int _counter;
public Debouncer(TimeSpan? waitTime = null)
{
Console.WriteLine("Creating debouncer");
_waitTime = waitTime ?? TimeSpan.FromSeconds(3);
}
public void Debouce(Action action)
{
var current = Interlocked.Increment(ref _counter);
Task.Delay(_waitTime).ContinueWith(task =>
{
if (current == _counter && !_cts.IsCancellationRequested)
{
Console.WriteLine("Invoking action");
action();
}
task.Dispose();
}, _cts.Token);
}
public void Dispose()
{
_cts.Cancel();
}
}
Пользователь
public class User : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
Console.WriteLine($"OnPropertyChanged: {propertyName}");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}