我需要使用异步
WaitForUpdate
方法在 C# 中实现一个相对简单的字典类。此方法应该允许异步等待指定键值的更新。默认情况下,当为引用类型 (TValue) 传递新引用或为值类型分配新值时,会发生更新。此外,如果集合实现了 IObservableCollection
接口,该方法应该监视集合内的更改。
如果添加或更新了值,则
WaitForUpdate
方法应立即返回。鉴于该类将在项目中大量使用,您对改进和优化该类有何建议?
理想情况下,我想避免使用
CollectionChangedHandlerController
并创建一个单独的字典来管理它。但是,我不确定如何在没有它的情况下有效控制事件处理程序(例如,防止触发删除或替换的集合的处理程序)。任何指导将不胜感激!
enum CollectionChangedAction {
ItemAdded, ItemRemoved, ItemUpdated, Reset
}
class CollectionChangedEventArgs : EventArgs {
public CollectionChangedAction Action { get; private set; }
public CollectionChangedEventArgs(CollectionChangedAction action) => Action = action;
}
interface IObservableCollection {
public event EventHandler<CollectionChangedEventArgs> CollectionChanged;
}
/// <summary>
/// Represents a thread-safe collection of key/value pairs that allows awaiting
/// notifications for value updates.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary</typeparam>
class AwaitableDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> {
private class DefaultEqualityComparer<T> : IEqualityComparer<T> {
public static DefaultEqualityComparer<T> Instance { get; } = new DefaultEqualityComparer<T>();
private DefaultEqualityComparer() { }
public bool Equals(T x, T y) {
if (default(T) == null) return ReferenceEquals(x, y);
return EqualityComparer<T>.Default.Equals(x, y);
}
public int GetHashCode(T obj) {
if (default(T) == null)
return obj != null ? RuntimeHelpers.GetHashCode(obj) : 0;
return EqualityComparer<T>.Default.GetHashCode(obj);
}
}
private class CollectionChangedHandlerController {
private EventHandler<CollectionChangedEventArgs> EventHandler;
public IObservableCollection Collection { get; private set; }
public CollectionChangedHandlerController(IObservableCollection collection) {
Collection = collection;
}
public void AddHandler(EventHandler<CollectionChangedEventArgs> eventHandler) {
if (eventHandler == null)
throw new ArgumentNullException(nameof(eventHandler));
if (EventHandler != null)
throw new InvalidOperationException("A handler is already registered for this collection change event");
if (Collection != null) {
EventHandler = eventHandler;
Collection.CollectionChanged += eventHandler;
}
}
public void RemoveHandler() {
if (EventHandler != null) {
Collection.CollectionChanged -= EventHandler;
EventHandler = null;
}
}
}
private readonly bool IsObservableCollectionInValue;
private readonly ConcurrentDictionary<TKey, TValue> InnerDictionary;
private readonly ConcurrentDictionary<TKey, CollectionChangedHandlerController> InnerCollectionsDictionary;
private readonly ConcurrentDictionary<TKey, TaskCompletionSource> Awaiters = new();
private readonly IEqualityComparer<TValue> ValueEqualityComparer = DefaultEqualityComparer<TValue>.Instance;
public int Count => IsObservableCollectionInValue ? InnerCollectionsDictionary.Count : InnerDictionary.Count;
public bool IsEmpty => IsObservableCollectionInValue ? InnerCollectionsDictionary.IsEmpty : InnerDictionary.IsEmpty;
public CollectionChangedAction[] TrackedChangeActions { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="AwaitableDictionary{TKey, TValue}"/>
/// class that is empty.
/// </summary>
public AwaitableDictionary() {
IsObservableCollectionInValue = typeof(TValue).IsAssignableTo(typeof(IObservableCollection));
if (!IsObservableCollectionInValue) InnerDictionary = new();
else {
InnerCollectionsDictionary = new();
TrackedChangeActions = [
CollectionChangedAction.ItemAdded,
CollectionChangedAction.ItemRemoved,
CollectionChangedAction.ItemUpdated,
CollectionChangedAction.Reset];
}
}
/// <summary>
/// Initializes a new instance of the <see cref="AwaitableDictionary{TKey, TValue}"/>
/// class that is empty.
/// </summary>
/// <param name="trackedChangeActions">
/// Specifies the tracked types of changes occurring in the collection.
/// </param>
public AwaitableDictionary(CollectionChangedAction[] trackedChangeActions) : this() {
if (trackedChangeActions == null)
throw new ArgumentNullException(nameof(trackedChangeActions));
if (!IsObservableCollectionInValue)
throw new NotSupportedException("Tracking change actions is only" +
" supported when TValue implements IObservableCollection");
TrackedChangeActions = trackedChangeActions.Distinct().ToArray();
}
/// <summary>
/// Initializes a new instance of the <see cref="AwaitableDictionary{TKey, TValue}"/>
/// class that contains elements copied from the specified <see cref="IDictionary{TKey, TValue}"/> object.
/// </summary>
/// <param name="dictionary">
/// The <see cref="IDictionary{TKey, TValue}"/> whose elements are copied to the new
/// <see cref="AwaitableDictionary{TKey, TValue}"/>.
/// </param>
/// <param name="valueEqualityComparer">
/// An optional equality comparer used to compare values and determine whether an update notification
/// should be triggered when a value is added or updated. If not specified, the default equality comparer for
/// <typeparamref name="TValue"/> is used.
/// </param>
public AwaitableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TValue> valueEqualityComparer = null) : this() {
if (dictionary == null)
throw new ArgumentNullException(nameof(dictionary));
InnerDictionary = new ConcurrentDictionary<TKey, TValue>(dictionary);
foreach (var key in dictionary.Keys) {
var completionSource = new TaskCompletionSource();
completionSource.SetResult();
Awaiters.TryAdd(key, completionSource);
}
if (valueEqualityComparer != null)
ValueEqualityComparer = valueEqualityComparer;
}
public void AddOrUpdate(TKey key, TValue value) {
if (IsObservableCollectionInValue) {
void onCollectionChanged(object sender, CollectionChangedEventArgs e) {
if (TrackedChangeActions.Contains(e.Action) &&
Awaiters.TryGetValue(key, out var completionSource) &&
!completionSource.Task.IsCompleted) {
completionSource.SetResult();
}
};
CollectionChangedHandlerController newValue = new((IObservableCollection)value);
newValue.AddHandler(onCollectionChanged);
InnerCollectionsDictionary.AddOrUpdate(key, newValue, (key, existingValue) => {
existingValue.RemoveHandler();
return newValue;
});
}
else {
bool newValueProduced = true;
InnerDictionary.AddOrUpdate(key, value, (key, existingValue) => {
newValueProduced = !ValueEqualityComparer.Equals(value, existingValue);
return value;
});
if (newValueProduced)
Awaiters.GetOrAdd(key, _ => new TaskCompletionSource()).TrySetResult();
}
}
public bool TryGetValue(TKey key, out TValue value) {
if (IsObservableCollectionInValue) {
if (InnerCollectionsDictionary.TryGetValue(key, out var handlerController)) {
value = (TValue)handlerController.Collection;
return true;
}
value = default;
return false;
}
else {
return InnerDictionary.TryGetValue(key, out value);
}
}
public bool TryRemove(TKey key) {
if (IsObservableCollectionInValue) {
if (!InnerCollectionsDictionary.TryRemove(key, out var handlerController))
return false;
handlerController.RemoveHandler();
}
else if (!InnerDictionary.TryRemove(key, out _)) {
return false;
}
if (Awaiters.TryRemove(key, out var completionSource))
completionSource.TrySetCanceled();
return true;
}
public void Clear() {
if (IsObservableCollectionInValue) {
var handlerControllers = InnerCollectionsDictionary.Values.ToArray();
InnerCollectionsDictionary.Clear();
foreach (var handlerController in handlerControllers)
handlerController.RemoveHandler();
}
else {
InnerDictionary.Clear();
}
var completionSources = Awaiters.Values.ToArray();
Awaiters.Clear();
foreach (var completionSource in completionSources)
completionSource.TrySetCanceled();
}
/// <summary>
/// Asynchronously waits for an update to the specified key's value.
/// If the value has already been added or modified, the method returns immediately,
/// after which it can be called again to track further changes to the value.
/// If <typeparamref name="TValue"/> implements <see cref="IObservableCollection"/>,
/// only changes to the collection's contents are tracked.
/// </summary>
public async ValueTask<TValue> WaitForUpdate(TKey key, CancellationToken cancellationToken) {
var completionSource = Awaiters.GetOrAdd(key, _ => new TaskCompletionSource());
await completionSource.Task.WaitAsync(cancellationToken);
Awaiters.TryUpdate(key, new TaskCompletionSource(), completionSource);
TValue value;
if (IsObservableCollectionInValue) {
if (!InnerCollectionsDictionary.TryGetValue(key, out var handlerController))
throw new OperationCanceledException();
value = (TValue)handlerController.Collection;
}
else {
if (!InnerDictionary.TryGetValue(key, out value))
throw new OperationCanceledException();
}
return value;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
if (IsObservableCollectionInValue) {
return InnerCollectionsDictionary.ToDictionary(pair => pair.Key,
pair => (TValue)pair.Value.Collection).GetEnumerator();
}
return InnerDictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
快速浏览一下,我有一些建议可以在代码中重复使用,因为您正在使用 .NET 8
CollectionChangedHandlerController 类
我看到您对 EventHandler 进行了空检查:
if (EventHandler != null)
throw new InvalidOperationException("A handler is already registered for this collection change event");
这意味着EventHandler是可为空的,因此您可以在属性上添加可为空的:
private Event<Handler>CollectionChangedEventArgs>? EventHandler;
可选更改:
private class CollectionChangedHandlerController(IObservableCollection collection) { public IObservableCollection Collection { get; } = collection; }