C# 自定义INotifyCollectionChanged - 实现集合唯一性
INotifyCollectionChanged
详见: [INotifyCollectionChanged 接口](https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.specialized.inotifycollectioncha nged?view=net-5.0).
//
// 摘要:
// 例如,当添加和删除项或清除整个列表时,向侦听器通知动态更改。
[TypeForwardedFrom("WindowsBase, Version=3.0.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
public interface INotifyCollectionChanged
{
//
// 摘要:
// 当集合更改时发生。
event NotifyCollectionChangedEventHandler CollectionChanged;
}
UniquenessCollection
[Serializable]
public class UniquenessCollection<T, TKey> : ICollection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
private List<T> m_ListCache = new List<T>();
private readonly IComparer<TKey> m_Comparer = Comparer<TKey>.Default;
private readonly Func<T, TKey> m_Func;
public UniquenessCollection(Func<T, TKey> uniquenessKeyFunc)
{
this.m_Func = uniquenessKeyFunc ?? throw new ArgumentNullException(nameof(uniquenessKeyFunc));
}
public int Count => this.m_ListCache.Count;
public bool IsReadOnly { get; set; } = false;
public T this[int index] => this.m_ListCache[index];
public ReadOnlyCollection<T> AsReadOnly()
{
return this.m_ListCache.AsReadOnly();
}
public IList<T> Items => this.m_ListCache;
public ReadOnlyCollection<TKey> Keys => this.Select(this.GetKey).AsReadOnly();
public bool Add(T item)
{
bool canAdd = this.CanAdd(item);
if (canAdd)
{
this.m_ListCache.Add(item);
this.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, this.m_ListCache.Count - 1));
}
return canAdd;
}
public virtual bool CanAdd(T item)
{
if (this.IsReadOnly)
{
return false;
}
object temp = this.GetKey2Obj(item);
return this.m_ListCache.FirstOrDefault(x => this.GetKey2Obj(x) == temp) == null;
}
public bool Contains(TKey key)
{
return this.m_ListCache.FirstOrDefault(x => this.m_Comparer.Compare(this.GetKey(x), key) == 0) != null;
}
public TKey GetKey(T item)
{
return this.m_Func.Invoke(item);
}
private object GetKey2Obj(T item)
{
return this.m_Func.Invoke(item);
}
public T Find(Predicate<T> match)
{
return match == null ? default : this.FirstOrDefault(x => match(x));
}
public bool Exist(Predicate<T> match)
{
if (match == null)
{
return false;
}
return this.FirstOrDefault(x => match(x)) != null;
}
#region ICollection<T>
void ICollection<T>.Add(T item)
{
this.Add(item);
}
public void Clear()
{
List<T> oldList = this.m_ListCache;
this.m_ListCache = new List<T>();
this.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldList, 0));
}
public bool Contains(T item)
{
return this.m_ListCache.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
this.m_ListCache.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
if (!this.IsReadOnly && this.m_ListCache.Contains(item))
{
int index = this.m_ListCache.IndexOf(item);
if (this.m_ListCache.Remove(item))
{
this.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
return true;
}
}
return false;
}
#endregion
#region IEnumerable<T>
public IEnumerator<T> GetEnumerator()
{
return this.m_ListCache.GetEnumerator();
}
#endregion
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
return this.m_ListCache.GetEnumerator();
}
#endregion
#region INotifyCollectionChanged
public event NotifyCollectionChangedEventHandler CollectionChanged;
public void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e != null)
{
if (e.NewItems != null)
{
this.OnAddNewItems(e.NewItems.Cast<T>().ToList());
}
if(e.OldItems !=null)
{
this.OnRemoveItems(e.OldItems.Cast<T>().ToList());
}
}
this.CollectionChanged?.Invoke(this, e);
this.RaisePropertyChanged(nameof(this.Count));
this.RaisePropertyChanged(nameof(this.Keys));
this.RaisePropertyChanged("Items[]");
}
protected virtual void OnRemoveItems(IList<T> oldItems)
{
}
protected virtual void OnAddNewItems(IList<T> newItems)
{
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string name = "")
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion
}
KeyOfStringUniquenessCollection
public class KeyOfStringUniquenessCollection<T> : UniquenessCollection<T, string>
{
public KeyOfStringUniquenessCollection(Func<T, string> uniquenessKeyFunc) : base(uniquenessKeyFunc)
{
}
public T this[string strKey] => this.FirstOrDefault(x => this.GetKey(x) == strKey);
public bool InternalAdd(T t)
{
bool success = this.Add(t);
if (!success)
{
DNService.MessageService.ShowError($"创建{typeof(T).Name}节点失败,未能将节点添加到{this.GetType().Name}中");
throw new PlcCreateNodeException($"创建{typeof(T).Name}节点失败,未能将节点添加到{this.GetType().Name}中");
}
return success;
}
public virtual string VerifyName(string sourceName, out bool isDifferent)
{
isDifferent = false;
if (this.Count <= 0)
{
return sourceName;
}
string tempName = sourceName;
for (int i = 1; ; ++i)
{
if (!this.Contains(tempName))
{
break;
}
tempName = sourceName + i;
}
isDifferent = tempName != sourceName;
return tempName;
}
public virtual string VerifyName(string sourceName, string[] exceptNames, out bool isDifferent)
{
isDifferent = false;
if (this.Count <= 0)
{
return sourceName;
}
List<string> keys = this.Keys.ToList();
if (exceptNames != null && exceptNames.Length > 0)
{
exceptNames.ForEach(x => keys.Remove(x));
}
string tempName = sourceName;
for (int i = 1; ; ++i)
{
if (!keys.Contains(tempName))
{
break;
}
tempName = sourceName + i;
}
isDifferent = tempName != sourceName;
return tempName;
}
public string TryGetName(string sourceName, string defaultName = "", bool throWhenIsDifferent = false)
{
string newName = "";
if (string.IsNullOrWhiteSpace(sourceName))
{
newName = this.VerifyName(string.IsNullOrWhiteSpace(defaultName) ? typeof(T).Name : defaultName, out _);
}
else
{
newName = this.VerifyName(sourceName, out bool isDifferent);
if (isDifferent)
{
if (throWhenIsDifferent)
{
throw new NameConflictException($"创建{typeof(T).Name}时名称冲突!").SetValue(sourceName, newName);
}
else
{
DNService.MessageService.ShowWarning($"创建{typeof(T).Name}时名称冲突!,已将名称{sourceName}改为{newName}");
}
}
}
#if DEBUG
newName.ThrowIfIsNullOrWhiteSpace();
ExceptionExtensions.ThrowInvalidOperationException(() => this.Contains(newName));
#endif
return newName;
}
}
ExceptionExtensions
public static class ExceptionExtensions
{
public static void ThrowIfIsNullOrWhiteSpace(this string value, string message = "")
{
if (string.IsNullOrWhiteSpace(value))
{
throw new System.ArgumentNullException($"Message:{message}");
}
}
public static void ThrowIfIsNull<T>(this T value, string message = "")
{
if (value == null)
{
throw new ArgumentNullException($"Type:{typeof(T).FullName},Message:{message}");
}
}
public static void ThrowArgumentException(Func<bool> func, string message)
{
func.ThrowIfIsNull();
if (func.Invoke())
{
throw new ArgumentException(message);
}
}
public static void ThrowInvalidOperationException(Func<bool> func, string message = "")
{
func.ThrowIfIsNull();
if (func.Invoke())
{
throw new InvalidOperationException(message);
}
}
public static void ThrowArgumentOutOfRangeException(Func<bool> func, string message = "")
{
func.ThrowIfIsNull();
if (func.Invoke())
{
throw new ArgumentOutOfRangeException(message);
}
}
}
UniquenCacheNodeCollection
/// <summary>
/// <see cref="CacheNode"/>的一个集合,具有名称唯一性
/// </summary>
[Serializable]
public class UniquenCacheNodeCollection : KeyOfStringUniquenessCollection<CacheNode>
{
private readonly bool m_CanBoth;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="canBoth">是否可以同时存在地址和标签值</param>
public UniquenCacheNodeCollection(bool canBoth = true) : base((n) => n.Name)
{
this.m_CanBoth = canBoth;
}
public override bool CanAdd(CacheNode item)
{
if (this.m_CanBoth)
{
return base.CanAdd(item);
}
bool canAdd = true;
if (this.Count > 0)
{
canAdd = this[0].PlcValueType == item.PlcValueType;
if (!canAdd)
{
#if DEBUG
DNService.MessageService.ShowWarning($"无法添加{nameof(CacheNode)},因为集合值类型为{this[0].PlcValueType},添加的值类型为{item.PlcValueType}");
#else
throw new InvalidOperationException($"无法添加{nameof(CacheNode)},因为集合值类型为{this[0].PlcValueType},添加的值类型为{item.PlcValueType}");
#endif
}
}
return canAdd && base.CanAdd(item);
}
}