How Dependency Properties are Stored in WPF






4.90/5 (20 votes)
This article provides in depth overview of how dependency properties are stored in WPF Property System.
Introduction
While at work, someone asked me a question: As we know that Dependency Properties in WPF are usually public static
and readonly
, but each instance of owner objects can have their individual value at the same time. How is this possible? This is not in accordance with our understanding of static
CLR properties. In this article, first we will be looking into few classes of WPF in order to answer this question. While we went into the discussion, it led to many more questions which we will be covering in the next article.
Note: We will use acronym "DP" instead of "Dependency Property" in the rest of the article for convenience of writing and reading. Basic understanding of WPF framework is a pre-requisite for this article.
Outline
- General DP Creation Code
- What happens inside
Register
method - How getter works for DP
- How setter works for DP
- How WPF Property System Reduces Memory footprint
General DP Creation Code
With this article, in the attached demo code, we have used two TextBox
es and initially will focus on "MinLines
" DP of TextBox
class. In MainWindow.xaml, we created two instances of TextBox
, object named as TextBoxOne
has local value of "MinLines
" DP as 2 while object named as TextBoxTwo
has local value of "MinLines
" DP as 4. While we run the application, both TextBox
es have different values for MinLines
DP as per their local values.
In order to understand working of DP, we will take an example of inbuilt "MinLines
" DP and see how the local values are stored. Before diving into how DP and its local values are stored, first let's see how TextBox
class defines "MinLines
" DP. The below code is taken from reference code of TextBox
available here.
/// <summary>
/// Dependency ID for the MinLines property
/// Default value: 1
/// </summary>
public static readonly DependencyProperty MinLinesProperty =
DependencyProperty.Register(
"MinLines", // Property name
typeof(int), // Property type
typeof(TextBox), // Property owner
new FrameworkPropertyMetadata(
1,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnMinMaxChanged)),
new ValidateValueCallback(MinLinesValidateValue));
/// <summary>
/// Minimum number of lines to size to.
/// </summary>
[DefaultValue(1)]
public int MinLines
{
get { return (int) GetValue(MinLinesProperty); }
set { SetValue(MinLinesProperty, value); }
}
Just by looking at the above code, we can notice many things different from normal CLR properties:
- While creating a DP, we just call
static
methodRegister
ofDependencyProperty
class. - Variable
MinLinesProperty
ispublic static readonly
and it is called identifier of DP. - Getter of DP is calling a method
GetValue
and there is casting toint
. - Setter of DP is also calling a method
SetValue
. - There is no backing
private
field for DP as we used to have for CLR properties.
And we are calling methods for registering DP and while getting or setting value the DP. The mechanism how DP works and stores its value is hidden in WPF Property System. Further, we will look closely at what happens when you call those methods.
What Happens inside Register Method
The brief working of Register Method of DependencyProperty class is given below:
- You call
static
method:Register
ofDependencyProperty
class with arguments while creating a DP. Register
method validates Metadata (if given) and calls the below method:DependencyProperty property = RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
- Inside
RegisterCommon
method, first, a key for DP is being created usingName
of dependency property and its owner type. - A class called as
FromNameKey
will be used asKey
. TheKey
maintains integerHashcode
for DP. It overridesGetHashCode
method and generatesHashCode
based on DP name and itsOwnerType
. DependencyProperty
class has aprivate HashTable
named asPropertyFromName
. ThenRegisterCommon
method checks if HashTable calledPropertyFromName
does not thatKey
already.- Then if metadata is not given, default metadata is being created.
- Then instance of DP is created using
new
keyword:// Create property DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
- New instance of DP is created and stored in
HashTable
calledPropertyFromName
as shown below:PropertyFromName[key] = dp;
- And newly created DP is returned to from
RegisterCommon
method toRegister
method ofDependencyProperty
class. - Finally,
Register
method returns the DP instance to the class which calledRegister
method to store that in identifier too for its use. In our case, variable/identifierMinLinesProperty
will receive the return value ofRegister
method.
Above is just a simplified summary of what happens, feel free to deep drive into the code - reference code available here. DependencyProperty
class maintains a static
reference of all the DependencyProperty
in a hashtable called PropertyFromName
. Each dependencyProperty
object is registered in that HashTable
.
There are total 5 different overloads of public static
Register
methods in DependencyProperty
class to register DP. Similarly, other 5 overloads of RegisterAttach
method to register Attached properties. For more, have a look at DependencyProperty Class detail on MSDN.
How getter Works for DP
The working of getter of a DP can be described as follows:
- The Getter works differently than CLR property getter. It does not return a value from a
private
field, but calls a methodGetValue(DependencyProperty)
fromDependencyObject
class. DependencyProperty
nowhere has a provision to store local value of DP. DP only has its default value and a method calledGetDefaultValue
which will return the default value of DP, that’s all.- One reason why DP can be defined only in a class inherited from
DependencyObject
, is that Getter of DP needs methodGetValue
which comes from base, i.e.,DependencyObject
class. - When you call
GetValue
, you pass the localpublic static
read only variable (identifier) which has a little information and metadata related to DP (but not value). GetValue
method makes a call to the following method:GetValueEntry( LookupEntry(dp.GlobalIndex), dp, null, RequestFlags.FullyResolved)
which returns a
struct
calledEffectiveValueEntry
.- Inside method
GetValueEntry
, for simplicity, the following code is most relevant here:entry = _effectiveValues[entryIndex.Index];
It looks into aprivate
array ofstruct
EffectiveValueEntry
called as_effectiveValues
which holds all the instance values for a DP of the class. This array is defined in classDepencentObject
as private instance member so all class inherited fromDepencentObject
will have and uses it._effectiveValues
works as the collection of effective values for theDependencyObjects
. struct
EffectiveValueEntry
has a field calledValue
which is ofobject
type so it can hold instance value of DP based on any data type.- Method
EntryIndex LookupEntry (int targetIndex)
takesGlobalIndex
(Zero-based globally unique index of the property) of DP and looks for an entry that matches the given DP. - Finally casting happens in getter of DP from object to particular data type (as return type of
GetValue
method is object).
Following is the code how _effectiveValues
is defined in class DependencyObject
:
// The cache of effective values for this DependencyObject
// This is an array sorted by DP.GlobalIndex. This ordering is
// maintained via an insertion sort algorithm.
private EffectiveValueEntry[] _effectiveValues;
and same private
array is exposed internal
property of class DependencyObject
as EffectiveValues
:
// The cache of effective (aka "computed" aka "resolved") property
// values for this DO. If a DP does not have an entry in this array
// it means one of two things:
// 1) if it's an inheritable property, then its value may come from
// this DO's InheritanceParent
// 2) if it's not an inheritable property (or this DO's InheritanceParent
// doesn't have an entry for this DP either), then the value for
// that DP on this DO is the default value.
// Otherwise, the DP will have an entry in this array describing the
// current value of the DP, where this value came from, and how it
// has been modified
internal EffectiveValueEntry[] EffectiveValues
{
[FriendAccessAllowed] // Built into Base, also used by Framework.
get { return _effectiveValues; }
}
How Setter Works for DP
The working of getter of a DP is as follows:
- Similar to Getter, Setter also works differently than CLR property setter. It does not store a value in a
private
field, but calls a methodSetValue
fromDependencyObject
class. - When you call
SetValue
, you pass thelocal public static
read only identifier and given value (casting happens as object). SetValue
method makes a call to the following method which returnsvoid
:SetValueCommon(dp, value, metadata, false, false, OperationType.Unknown, false)
- Inside method
SetValueCommon
, first it will look for entry index in_effecticeValues
array.EntryIndex entryIndex = LookupEntry(dp.GlobalIndex);
- And then
struct
EffectiveValueEntry
is created for given DP and finallySetValueCommon
will call following methods toinsert
/update
value in_effectiveVaues
array:SetEffectiveValue(entryIndex, dp, dp.GlobalIndex, metadata, newExpr, BaseValueSourceInternal.Local); ................. ................. UpdateEffectiveValue(entryIndex, dp, metadata, oldEntry, ref newEntry, coerceWithDeferredReference, coerceWithCurrentValue, operationType);
- Both
SetValue
andSetValueCommon
methods arevoid
so return nothing.
For a side note, there are following methods in class DependencyObject
which updates values in _effectiveVaues
array being called during different operations:
private EffectiveValueEntry GetEffectiveValue(
EntryIndex entryIndex, DependencyProperty dp,
RequestFlags requests)
private void InsertEntry(
EffectiveValueEntry entry, uint entryIndex)
internal void SetEffectiveValue(
EntryIndex entryIndex, DependencyProperty dp,
PropertyMetadata metadata, EffectiveValueEntry newEntry,
EffectiveValueEntry oldEntry)
internal void SetEffectiveValue(
EntryIndex entryIndex, DependencyProperty dp,
int targetIndex, PropertyMetadata metadata,
object value, BaseValueSourceInternal valueSource)
private void SetExpressionValue(
EntryIndex entryIndex, object value, object baseValue)
How WPF Property System Reduces Memory Footprint
Now let's summarize what we concluded about how DP is stored internally in image given below:
Besides, let's see how CLR properties are stored. For example, if a class called Customer
is having 100 CLR properties, then while you create an instance of Customer
, the memory for all 100 CLR properties will be allocated in Heap
.
Now let's take a TextBox class in WPF, which has more than 150 dependency properties, but until we set local values, there will be no memory consumed for a DP to store local values for created owner instance. Still, you can use getter and it will return value, either its default value or as per Dependency Property Value Resolution mechanism. As we have seen, default values of DPs are not owner instance specific like different TextBox
es. Default value of DP is stored with global
DP static
object in PropertyFromName
HashTable
(instance of DependencyProperty
class for MinLines
in our example) .
So in case of DP, the local values for instance consumes memory only if they are set. This is how WPF property system helps to reduce memory footprint of an application.
Conclusion
In this article, we looked into Dependency Property creation and how its getter and setter work. We understood how Dependency Property storage mechanism helps to reduce memory footprint of an application. Thanks for reading. Your comments and suggestions for improvement are most welcome.