A dependency property is a property on one object whose value depends on some other object. So, for instance.
- The value of the FontFamily property of a TextBox can (and usually does) depend on the FontFamily property of its container. If you change the property on the container, the value on the TextBox changes.
- The value of the Text property of a TextBox can depend on a bound data source. When the value of the bound property changes, the value of the Text property changes.
- The value of the
Opacityproperty of a
Labelcan depend on an animation storyboard, in the common scenario where you’ve set up a UI element to fade in or fade out in response to some event.
- The value of all kinds of properties on any UI element can depend on the style that you’ve applied to them.
The central concept behind dependency is that the thing that’s depending should get the property value from the thing it’s depending on. This is why a dependency property is implemented as a CLR property whose getter calls a method.
When the TextBox, or the thing that’s rendering it, needs to know what its FontFamily is, the FontFamily getter calls the GetValue method. That method finds the value from the container, or animation, or binding, or whatever.
There’s a lot of complexity in that method. If the value’s inherited, for instance, it works in a way that’s pretty analogous to how WPF finds styles in a resource dictionary: it looks in a local dictionary to find the value, and if there’s no entry it looks in its parent’s dictionary, and so on all the way up until it finds a value or reaches the top of the hierarchy, in which case it uses a default value.
If you look at the implementation of dependency properties, that’s what you’ll find. A dependency object has a dictionary that may or may not contain an entry for a given property. The GetValue method gets values from that dictionary (which is how objects with dependency properties can have local values that override what they’re inheriting from), and if it doesn’t find the value, it uses the metainformation about the property to figure out where it should look.
Since that metainformation is the same for every object in the class (i.e., TextBox.Text works the same for every TextBox), the dictionary it’s stored in is a static property of the class.
So when you see code like this:
what’s happening is that the metainformation that defines the IsDefault property on all Button objects is being added to that dictionary. And when you see this:
what you’re seeing is the getter method that looks up the property’s value (from the local dictionary, the parent object, or whatever) based on that metainformation.
Remember how I said that the first place the getter looks to find a property’s value is in the object’s local dictionary? The SetValue method in the setter is how that entry gets added to the dictionary (if it’s ever called, which it will only be if you’re overriding the dependency by explicitly setting the property, i.e. saying "I want this TextBox to display text in Consolas irrespective of what the other controls in the window are using.").
A hugely significant benefit that we get from this kind of apparently-complex system is that dependency properties on objects only consume memory if they’re set. If I create 10,000 TextBox controls and add them to a Window, not one of them actually contains a reference to a FontFamily object. That’s 10,000 object references that I’m not allocating memory for, and that the garbage collector isn’t checking. In fact, if a TextBox has 100 dependency properties (and it does, just about), whenever you create a TextBox, that’s 100 backing fields you aren’t allocating memory for. Dependency properties only consume memory if you explicitly set them. Since the vast majority of properties on UI objects never get explictly set, these are fantastic savings.