这样的绑定确实有效,但会造成内存泄漏。框架将创建对源对象ConduitCapacity 的静态引用来观察它。由于静态引用永远不符合垃圾收集器的条件,因此静态对象引用将使对象ConduitCapacity 保持活动状态,从而阻止它被收集。这也适用于绑定到未实现 INotifyCollectionChanged 的集合时。
如果您担心避免内存泄漏,那么数据绑定的源必须实现INotifyPropertyChanged、INotifyCollectionChanged 或属性必须是DependencyProperty。
DependencyProperty 提供最佳性能。这意味着当源对象是DependencyObject 时,您应该更愿意将旨在用作绑定源的属性实现为DependencyProperty。
更新:
不遵循INotifyPropertyChanged 或DependencyProperty 绑定模式时数据绑定的工作原理以及如何使用此方法启用TwoWay 绑定
在评论区讨论了一些之后,我觉得有必要更新问题来解释一下背景。
结合Microsoft Docs: How Data Binding References are Resolved 提供的信息和微软知识库文档
KB 938416,
我们了解 WPF 使用三种方法来建立从 DependencyProperty(绑定目标)到任何 CLR 对象(绑定源)的数据绑定:
-
TypeDescrtiptor(组件检测)
INotifyPropertyChanged
DependencyProperty
最初的问题与方法 1) 有关:创建与源的数据绑定,该数据绑定未实现 INotifyPropertyChanged 或 DependencyProperty。因此,框架不得不利用重磅TypeDescriptor来设置属性变化的绑定和跟踪。
从 KB 938416 文档中我们了解到,绑定引擎将存储对获得的PropertyDescriptor 的静态引用(通过使用TypeDescriptor)。由于通过这种方式获取PropertyDescriptor 非常慢,所以PropertyDescriptor 引用存储在静态HashTable 中(以避免连续的组件检查)。
框架使用这个PropertyDescriptor 来监听属性变化。现在,因为描述符实例存储在静态 HashTable 中,所以它永远无法进行垃圾回收。
静态引用或对象通常从不由 farbage 收集器管理。
因此内存泄漏,因为静态引用将使源对象在应用程序的生命周期内保持活动状态。
要解锁TwoWay 绑定,我们必须显式启用支持,以使PropertyDescriptor 了解Binding.Source 上的属性更改。
我们可以通过查询PropertyDescriptor.SupportsChangeEvents 属性来测试这种意识。是true,当:
- 我们使用
DependencyObject.SetValue来修改属性(也就是说属性也是DependencyProperty)或者
-
Binding.Source 提供一个事件,该事件必须符合以下命名模式:“[property_name]Changed”
这意味着,如果没有额外的事件,与普通 CLR 对象的绑定只能是 OneTime 或 OneWayToSource。从源到目标的初始化将始终有效。
示例
CLR 对象
绑定源,未实现INotifyPropertyChanged,但仍支持TwoWay绑定。
class ClrObject
{
public string TextProperty { get; set; }
// Event to enable TwoWay data binding
public event EventHandler TextPropertyChanged;
protected virtual void OnTextPropertyChanged()
=> this.TextPropertyChanged?.Invoke(this, EventArgs.Empty);
}
这就是 WPF 框架正在做的事情:
// Simplified. Would use reflection and binding engine lookup table to retrieve the binding.
// Example references a TextBox control named "BindingTarget" for simplicity
Binding binding = BindingOperations.GetBinding(this.BindingTarget, TextBox.TextProperty);
// Only observe Binding.Source when binding is TwoWay or OneWay
if (binding.Mode != BindingMode.OneWay
&& binding.Mode != BindingMode.TwoWay)
{
return;
}
object bindingSource = binding.Source ?? this.BindingTarget.DataContext;
// Use heavy TypeDescriptor inspection to obtain the object's PropertyDescriptors
PropertyDescriptorCollection descriptors = TypeDescriptor.GetProperties(bindingSource);
foreach (PropertyDescriptor descriptor in descriptors)
{
if (descriptor.Name.Equals(binding.Path.Path, StringComparison.OrdinalIgnoreCase)
&& descriptor.SupportsChangeEvents)
{
// Add descriptor to static HashTable for faster lookup
// (e.g. in case for additional data bindings to this source object).
// TypeDescriptor is too slow
// Attach a change delegate
descriptor.AddValueChanged(bindingSource, UpdateTarget_OnSourcePropertyChanged);
break;
}
}
我们可以看到为什么这种数据绑定方式表现不佳。 TypeDescriptor 很慢。此外,引擎必须使用更多反射来找到TextPropertyChanged 事件来初始化TwoWay 绑定。
我们可以得出结论,即使不是因为内存泄漏,我们也会避免这种解决方案,而是在 CLR 对象上实现INotifyCollectionChanged,或者更好地将属性实现为DependencyProperty(如果源是@987654371 @) 显着提高应用程序的性能(一个应用程序通常定义数百个绑定)。