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.
public static readonly DependencyProperty MinLinesProperty =
DependencyProperty.Register(
"MinLines", typeof(int), typeof(TextBox), new FrameworkPropertyMetadata(
1,
FrameworkPropertyMetadataOptions.AffectsMeasure,
new PropertyChangedCallback(OnMinMaxChanged)),
new ValidateValueCallback(MinLinesValidateValue));
[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
method Register
of DependencyProperty
class.
- Variable
MinLinesProperty
is public static readonly
and it is called identifier of DP.
- Getter of DP is calling a method
GetValue
and there is casting to int
.
- 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
of DependencyProperty
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 using Name
of dependency property and its owner type.
- A class called as
FromNameKey
will be used as Key
. The Key
maintains integer Hashcode
for DP. It overrides GetHashCode
method and generates HashCode
based on DP name and its OwnerType
.
DependencyProperty
class has a private HashTable
named as PropertyFromName
. Then RegisterCommon
method checks if HashTable called PropertyFromName
does not that Key
already.
- Then if metadata is not given, default metadata is being created.
- Then instance of DP is created using
new
keyword:
DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType,
defaultMetadata, validateValueCallback);
- New instance of DP is created and stored in
HashTable
called PropertyFromName
as shown below:
PropertyFromName[key] = dp;
- And newly created DP is returned to from
RegisterCommon
method to Register
method of DependencyProperty
class.
- Finally,
Register
method returns the DP instance to the class which called Register
method to store that in identifier too for its use. In our case, variable/identifier MinLinesProperty
will receive the return value of Register
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 method GetValue(DependencyProperty)
from DependencyObject
class.
DependencyProperty
nowhere has a provision to store local value of DP. DP only has its default value and a method called GetDefaultValue
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 method GetValue
which comes from base, i.e., DependencyObject
class.
- When you call
GetValue
, you pass the local public 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
called EffectiveValueEntry
.
- Inside method
GetValueEntry
, for simplicity, the following code is most relevant here:
entry = _effectiveValues[entryIndex.Index];
It looks into a private
array of struct
EffectiveValueEntry
called as _effectiveValues
which holds all the instance values for a DP of the class. This array is defined in class DepencentObject
as private instance member so all class inherited from DepencentObject
will have and uses it. _effectiveValues
works as the collection of effective values for the DependencyObjects
.
struct
EffectiveValueEntry
has a field called Value
which is of object
type so it can hold instance value of DP based on any data type.
- Method
EntryIndex LookupEntry (int targetIndex)
takes GlobalIndex
(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
:
private EffectiveValueEntry[] _effectiveValues;
and same private
array is exposed internal
property of class DependencyObject
as EffectiveValues
:
internal EffectiveValueEntry[] EffectiveValues
{
[FriendAccessAllowed] 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 method SetValue
from DependencyObject
class.
- When you call
SetValue
, you pass the local public static
read only identifier and given value (casting happens as object).
SetValue
method makes a call to the following method which returns void
:
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 finally SetValueCommon
will call following methods to insert
/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
and SetValueCommon
methods are void
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.
References