这是我一直希望有人为我编写的关于依赖属性如何工作的解释。它是不完整的,很可能是错误的,但它会帮助你对它们有足够的理解,从而能够掌握你阅读的文档。
依赖属性是类似属性的值,通过DependencyObject 类的方法获取和设置。它们可以(并且通常确实)看起来非常像 CLR 属性,但事实并非如此。这是关于他们的第一个令人困惑的事情。一个依赖属性实际上是由几个组件组成的。
这是一个例子:
Document 是RichTextBox 对象的属性。这是一个真正的 CLR 属性。也就是说,它有一个名称、一个类型、一个 getter 和一个 setter,就像任何其他 CLR 属性一样。但与“普通”属性不同的是,RichTextBox 属性不仅仅获取和设置实例内部的私有值。在内部,它是这样实现的:
public FlowDocument Document
{
get { return (FlowDocument)GetValue(DocumentProperty); }
set { SetValue(DocumentProperty, value); }
}
当您设置Document 时,您传入的值将与DocumentProperty 一起传递给SetValue。 那个是什么?而GetValue 又是如何获得它的价值的呢?还有……为什么?
首先是什么。在RichTextBox 上定义了一个名为DocumentProperty 的静态属性。当这个属性被声明时,它是这样完成的:
public static DependencyProperty DocumentProperty = DependencyProperty.Register(
"Document",
typeof(FlowDocument),
typeof(RichTextBox));
在这种情况下,Register 方法告诉依赖属性系统 RichTextBox - 类型,而不是实例 - 现在有一个名为 Document 的类型为 FlowDocument 的依赖属性。此方法存储此信息...某处。确切地说,是对我们隐藏的实现细节。
当Document 属性的setter 调用SetValue 时,SetValue 方法查看DocumentProperty 参数,验证它确实是属于RichTextBox 的属性并且value 是正确的类型,然后将其新值存储在某个地方。 DependencyObject 的文档在此实现细节上含糊其辞,因为您实际上并不需要知道它。在我关于这些东西如何工作的心智模型中,我假设有一个 Dictionary<DependencyProperty, object> 类型的属性是 DependencyObject 私有的,所以派生类(如 RichTextBox)看不到它,但 GetValue 和 SetValue可以更新它。但谁知道呢,说不定是和尚写在羊皮纸上的。
无论如何,这个值现在就是所谓的“本地值”,也就是说,它是这个特定 RichTextBox 的本地值,就像普通属性一样。
所有的重点是:
- CLR 代码不需要知道属性是依赖属性。它看起来与任何其他属性完全一样。您可以调用
GetValue 和SetValue 来获取和设置它,但除非您使用依赖属性系统做某事,否则您可能不需要。
- 与普通属性不同,在获取和设置它时,可能会涉及到它所属的对象以外的东西。 (可以想象,您可以通过反思来做到这一点,但反思很慢。在字典中查找内容很快。)
- 这个东西——它是依赖属性系统——本质上位于一个对象和它的依赖属性之间。它可以做各种各样的事情。
什么样的东西?好吧,让我们看一些用例。
绑定。当您绑定到一个属性时,它必须是一个依赖属性。这是因为Binding 对象实际上并没有在目标上设置属性,它在目标对象上调用SetValue。
样式。当您将对象的依赖属性设置为新值时,SetValue 会告诉样式系统您已经这样做了。这就是触发器的工作原理:它们不会发现属性的值通过魔法发生了变化,依赖属性系统会告诉它们。
动态资源。如果您编写像Background={DynamicResource MyBackground} 这样的XAML,您可以更改MyBackground 资源的值,并且引用它的对象的背景会更新。这也不是魔术。动态资源调用SetValue。
动画。动画通过操纵属性值来工作。这些必须是依赖属性,因为动画正在调用SetValue 来获取它们。
更改通知。注册依赖属性时,还可以指定SetValue在设置属性值时调用的函数。
值继承。 注册依赖属性时,可以指定它参与属性值继承。当您调用GetValue 来获取对象的依赖属性的值时,GetValue 会查看是否存在本地值。如果没有,它会向上遍历父对象链,查看该属性的它们的本地值。
这就是您可以在Window 上设置FontFamily 的方式,并且神奇地(我经常使用这个词)窗口中的每个控件都使用新字体。此外,您可以在一个窗口中拥有数百个控件,而每个控件都没有 FontFamily 成员变量来跟踪其字体(因为它们没有本地值),但您仍然可以设置 FontFamily在任何一个控件上(因为每个 DependencyObject 都有的 seekrit 隐藏值字典)。