我最近开始研究一个WPF应用程序,该应用程序具有一个包含大量数据的DataGrid。 ItemsSource的绑定方式如下:
ItemsSource={Binding MyData, Mode=OneWay}
MyData
是实现以下接口的自定义集合:
public sealed class GridCollection
: IList<object[]>, INotifyCollectionChanged, INotifyPropertyChanged
基础数据永远不会更改,而只会在某些触发器上完全重新加载。该馆藏拥有大量数据。由于列可以在重新生成数据源后发生更改,因此它们会在每个ItemsChanged(在继承的类中)之后(如下所示(很可能与问题无关,但谁知道))进行重建:
using (Dispatcher.DisableProcessing())
{
Columns.Clear();
for (var i = 0; i < columns.Count; i++)
{
var bindingBase = new Binding("[" + i + "]") {Mode = BindingMode.OneTime};
var column = new DataGridTextColumn
{
Header = columns[i].Name,
Binding = bindingBase,
IsReadOnly = true
};
Columns.Add(column);
}
}
现在的问题是,DataGrid似乎在其自己的ObservableCollection
中保存了集合中数据的完整副本,正如我在内存分析器中可以清楚地看到的那样(直接进入大对象堆) )。我只能假设此行为是为了避免多线程问题,或者它与GridView有关?可能是因为DataGrid无法将ItemsSource识别为List,而只能识别为IEnumerable?我还可以确认WPF制作了ItemsSource的副本之后,从未访问过集合中的数据。
我想要实现的是DataGrid直接在我的集合上运行,以提高内存效率和时间效率(因为创建此副本肯定需要时间)。
我已经玩过诸如CollectionSynchronization之类的东西,因为我认为也许如果我告诉我我负责同步,那么它就不会复制,
BindingOperations.EnableCollectionSynchronization(gridCollection, _collectionLock);
但它不会改变行为。我还确保行虚拟化可以在网格上正常工作。
有人知道为什么网格保留此副本以及如何避免这种情况吗?我的意思是肯定它可能必须保留一些数据(至少对于可见行),但是对于整个集合来说似乎有些过分……
更新 这些行没有重复,但是CollectionView创建了一个ObservableCollection,该数组具有引用我集合中所有条目的大型后备数组。 实现ICollectionView为我解决了该问题。现在,我得到了最奇怪的行为:
public IEnumerator<object[]> GetEnumerator()
{
lock (_lock)
{
if (_backingField == null) // Breakpoint hits when first refreshing view, but not second time!
yield break;
// Breakpoint hits second time, _backingField is null --> Exception
for (var i = 0; i < _backingField.Count; i++) yield return _backingField[i];
}
}
所以我第二次刷新视图时,不执行空检查。我将对_backingField的所有访问都放在一个锁中,_backingField是易失性的,并且禁用了编译优化。听起来像代码对我重新排序,但我什至在null检查和for之间放置了一个内存屏障,但这无济于事。这是WPF特定于ICollectionView的吗?我再次检查了断点是否始终在主线程上。有什么想法吗?
更新2 现在完全有道理,因为它是IEnumerable,所以在调试器中没有命中第二个检查。以某种方式实现ICollectionView会使DataGrid(或其他人)开始枚举,并在引发OnCollectionChanged重置事件后继续进行此操作。仍然不知道为什么,由于在resets事件之前背景字段已更改,因此枚举器此时无效。如果您知道最佳做法,请告诉我:)