我当前的项目围绕读取、修改和写入字节集合。我正在通过使用
ObservableCollection<byte>
并订阅其 CollectionChanged
事件来观察集合的更改。
现在,当某些字节发生更改时,我需要通过更新同一集合中的一些“其他”字节来对此更改做出反应。但是,ObservableCollection<T>
不允许在
CollectionChanged
事件期间通过使用 BlockReentrancy()
在其 OnCollectionChanged()
方法中返回的 diposable 并在其修改方法中调用 CheckReentrancy()
作为保护语句来修改集合 (Add()
、Remove()
等)。请参阅下面的代码,取自Microsoft 的参考源。
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
using (BlockReentrancy())
{
CollectionChanged(this, e);
}
}
}
在我的具体情况下,唯一的变化是
NotifyCollectionChangedAction.Replace
操作。我永远不会添加、删除或移动项目。在与同事讨论这个问题时,我们得出的结论是,在这种特殊情况下,我可以直接忽略重入。然后我想出了这个定制的
UnsafeObservableCollection<T>
(措辞可能不太理想,请怜悯)。
public class UnsafeObservableCollection<T> : ObservableCollection<T>
{
public UnsafeObservableCollection() { }
public UnsafeObservableCollection(IEnumerable<T> collection) : base(collection) { }
public UnsafeObservableCollection(List<T> list) : base(list) { }
public override event NotifyCollectionChangedEventHandler? CollectionChanged;
// ignoring reentrancy, allowing for collection updates within a collection changed event
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) => CollectionChanged?.Invoke(this, e);
}
我仍然不确定这可能会产生什么影响,即使我“只是”更换物品。我会遇到不一致的状态、无限更新循环或类似的问题吗?
我是否要做一些愚蠢的事情,或者考虑到我的具体情况,这是否合理?
我希望有人可以帮助我更好地理解和/或告诉我是否错过了可能会发生这种情况的场景。干杯
问题是,为什么要通知听众有关更改并允许报告的更改无效和过时?过时了,因为在通知听众时集合再次发生了变化?事件参数提供有关更改的非常具体的信息,例如更改的项目及其索引。它可能会使您的应用程序处于不可预测的状态。
例如,当有三个参与者时:集合所有者和两个观察者。
现在所有者将索引 1 处的项目替换为新项目,然后通知其他两个观察者有关更改的信息。
观察者 #1 是第一个收到通知的。事件参数报告索引 1 处的更改。观察者 #1 删除此项目(或替换它,操作类型并不重要)。此时观察者 #2 仍在等待原始通知。
我认为像
UnsafeObservableCollection
这样的可观察对象失去对事件相关状态的控制并因此通知观察者无效的事件数据有什么好处。
如果有多个观察者,修改
ObservableCollection
将导致其抛出 InvalidOperationException
异常。
一旦有两个或更多观察者,集合的通知就会变得危险,因为它们可能会给应用程序带来不可预测的状态 - 因此会出现强制应用程序停止的异常。
结论是,如果您可以保证在集合的生命周期内只有一个侦听器,或者没有侦听器依赖于事件数据,您可能可以避免阻止重入。
ItemsControl
上下文中,您已经至少有两个严重依赖于更改详细信息的观察者:绑定引擎(将更改转发到集合视图)和
ItemsControl
也跟踪更改管理其物品容器。
并且不要忘记从修改集合的方法中调用
CheckReentrancy
,以防保留重入阻塞行为。如果你的变异方法不调用
BlockReentrancy
,那么调用 CheckReentrancy
是没有意义的。它们是串联的,否则阻止重入将不起作用。
实现稳定可靠的
ObservableCollection
的一个好方法是正确使用覆盖:
public class SafeCustomObservableCollection<T> : ObservableCollection<T>
{
// Constructors ...
// Honor the dangers of reentrancy for the collection's client code.
// ObservableCollection only allows reentrancy when there is a single observer.
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
// Some additional behavior
// Delegate reentrancy control to the base class
// by invoking the CollectionChanged event via the existing base members.
base.OnCollectionChanged(e);
}
// Example override of a modifying method
protected override void SetItem(int index, T item)
{
// Implement some extended behavior (do not change the state)
// Delegate reentrancy control to base class
base.SetItem(index, item);
}
// If you completely override the behavior of ObservableCollection
// e.g. the way items are stored and managed,
// you must ensure a predictable state by invoking the
// protected base members for this purpose
protected override void SetItem(int index, T item)
{
// Delegate reentrancy control to the base class.
// Will throw if there is an incomplete notification procedure ongoing.
CheckReentrancy();
// Implement and override behavior
}
}