Here's the explanation for how dependency properties work that I always wished someone had written for me. It's incomplete and quite possibly wrong, but it will help you develop enough of an understanding of them that you can will be able to grasp the documentation that you read.
Dependency properties are property-like values that are get and set via methods of the DependencyObject
class. They can (and generally do) look very much like CLR properties, but they're not. And this gets to the first confusing thing about them. A dependency property is really made up of a couple of components.
Here's an example:
Document
is a property of the RichTextBox
object. It's a real CLR property. That is to say, it's got a name, a type, a getter, and a setter, just like any other CLR property. But unlike "normal" properties, the RichTextBox
property doesn't merely get and set a private value inside the instance. Internally, it's implemented like this:
public FlowDocument Document
{
get { return (FlowDocument)GetValue(DocumentProperty); }
set { SetValue(DocumentProperty, value); }
}
When you set Document
, the value you passed in gets passed to SetValue
, along with DocumentProperty
. And what is that? And how does GetValue
get its value? And ...why?
First the what. There's a static property defined on the RichTextBox
named DocumentProperty
. When this property is declared, it's done like this:
public static DependencyProperty DocumentProperty = DependencyProperty.Register(
"Document",
typeof(FlowDocument),
typeof(RichTextBox));
The Register
method, in this case, tells the dependency property system that RichTextBox
- the type, not the instance - now has a dependency property named Document
of type FlowDocument
. This method stores this information...somewhere. Where, exactly, is an implementation detail that's hidden from us.
When the setter for the Document
property calls SetValue
, the SetValue
method looks at the DocumentProperty
argument, verifies that it's really a property that belongs to RichTextBox
and that value
is the right type, and then stores its new value...somewhere. The documentation for DependencyObject
is coy on this implementation detail, because you don't really need to know it. In my mental model of how this stuff works, I assume there's a property of type Dictionary<DependencyProperty, object>
that's private to the DependencyObject
, so derived classes (like RichTextBox
) can't see it but GetValue
and SetValue
can update it. But who knows, maybe it's written on parchment by monks.
At any rate, this value is now what's called a "local value," which is to say it's a value that's local to this specific RichTextBox
, just like an ordinary property.
The point of all this is:
- CLR code doesn't need to know that a property is a dependency property. It looks exactly like any other property. You can call
GetValue
and SetValue
to get and set it, but unless you're doing something with the dependency property system, you probably don't need to.
- Unlike a normal property, something other than the object that it belongs to can be involved in getting and setting it. (You could do this with reflection, conceivably, but reflection is slow. Looking things up in dictionaries is fast.)
- This something - which is the dependency property system - essentially sits between an object and its dependency properties. And it can do all kinds of things.
What kinds of things? Well, let's look at some use cases.
Binding. When you bind to a property, it has to be a dependency property. This is because the Binding
object doesn't actually set properties on the target, it calls SetValue
on the target object.
Styles. When you set an object's dependency property to a new value, SetValue
tells the style system that you've done so. That's how triggers work: they don't find out that a property's value has changed through magic, the dependency property system tells them.
Dynamic resources. If you write XAML like Background={DynamicResource MyBackground}
, you can change the value of the MyBackground
resource, and the background of the object referencing it gets updated. This isn't magic either; the dynamic resource calls SetValue
.
Animations. Animations work by manipulating property values. Those have to be dependency properties, because the animation is calling SetValue
to get at them.
Change notification. When you register a dependency property, you can also specify a function that SetValue
will call when it sets the property's value.
Value inheritance. When you register a dependency property, you can specify that it participate in property value inheritance. When you call GetValue
to get the value of an object's dependency property, GetValue
looks to see if there's a local value. If there's not, it traverses up the chain of parent objects looking at their local values for that property.
This is how it is that you can set the FontFamily
on a Window
and magically (I'm using that word a lot) every control in the window uses the new font. Also, it's how it is that you can have hundreds of controls in a window without each of them having a FontFamily
member variable to track their font (since they don't have local values) but you can still set the FontFamily
on any one control (because of the seekrit hidden dictionary of values that every DependencyObject
has).