Introduction
Microsoft Corporation has introduced
DefaultValueAttribute
class in the .NET version 1.1. Ambiguity in its documentation created some confusion in terms of how this class should be used and how it should perform. To eliminate confusion, Microsoft issued Knowledge Base article
311339 where it states: “The DefaultValue
attribute does not cause the initial value to be initialized with the attribute's value.” So the implementation of this behavior is left for us, developers.
This article explains how to create lightweight and efficient algorithm to initialize class properties with values from DefaultValue
attributes. Different approaches are implemented, tested, and analyzed. It also demonstrates how to implement this as an AOP (Aspect Oriented Programming) extension which can be used directly with any C# class.
Background
DefaultValueAttribute
annotates properties with default value for that property. This information is saved with metadata of the type and can be used to describe it to the runtime, or to design time tools and environments. Detailed information for
DefaultValueAttribute
class is available at
MSDN library. A typical example of this attribute usage would be something like this:
public class TestClass
{
[DefaultValue(1)]
public int IntProperty { get; set; }
[DefaultValue(1)]
public long LongProperty { get; set; }
[DefaultValue(true)]
public bool BoolProptrty { get; set; }
}
*Sample code within this article is simplified for clarity purposes. For complete implementation, please download source code.
Various Approaches and Implementations
Native Initialization
The easiest and the most straightforward approach would be to initialize required attributes manually in the constructor like this:
public Constructor()
{
IntProperty = 1;
LongProperty = 1;
BoolProptrty = true;
}
public Constructor(int i)
{
IntProperty = 1;
LongProperty = 1;
BoolProptrty = true;
}
The main benefit of this solution is simplicity and very compact and fast code. The drawback of this approach is that it is "manual". It requires synchronization between the values set in the DefaultValueAttribute
and all of the constructors of this type done by hand and is subject to all possible human errors.
Initialization using ComponentModel Services
In my search for a solution to this problem, I came across an article written by
Daniel Stutzbach where he proposes simple yet effective algorithm which iterates through the list of the class’ properties and sets default values according to DefaultValue
attributes associated with its members:
public Constructor()
{
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this))
{
DefaultValueAttribute attr = prop.Attributes[typeof(DefaultValueAttribute)]
as DefaultValueAttribute;
if (attr != null)
prop.SetValue(this, attr.Value);
}
...
}
During construction of the class, this algorithm iterates through each property and checks if the property has a DefaultValueAttribute
associated with it. If such an attribute does exist, it initializes the property with a value from that attribute.
The good thing about this algorithm is that it eliminates error-prone manual synchronization and allows value initialization to be more consistent with various design tools.
The bad thing is that it is not very efficient. The algorithm uses Reflection services and iterates through all of the properties every time a new object is created.
Initialization using PropertyDescriptor.ResetValue(...)
The next method is very similar to the ComponentModel
Initialization approach except it uses method of PropertyDescriptor
class called ResetValue(…). According to MSDN documentation, this method resets property to the value determined in the following order of precedence:
- There is a shadowed property for this property.
- There is a
DefaultValueAttribute
for this property. - There is a "
ResetMyProperty
" method that has been implemented, where "MyProperty
" is the name of the property being reset.
So the code for this implementation looks like this:
public Constructor()
{
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(this))
property.ResetValue(this);
...
}
This approach creates a more flexible solution as opposed to the direct querying of DefaultValue
attributes. It provides alternative ways of resetting a property’s value but does not improve its performance.
Initialization with Delegates
The previous two algorithms are relatively slow. A lot of computer cycles are spent on iterating through collections and searching for correct methods to call. Their performance could be improved if all the ResetValue
methods were resolved only once and if their references were stored in cache for later use.
private static Action<object><this> setter;
public Constructor()
{
if (null == setter)
{
setter = (o) => { };
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this))
{
if (prop.CanResetValue(this))
setter += prop.ResetValue;
}
}
setter(this);
}
This algorithm is similar to the previous two, but instead of calling ResetValue
method on each property, it adds the method’s reference to the multicast delegate. Once all of the properties are iterated, invoking this delegate resets them to the appropriate values. Each consecutive instantiation of the class will simply call this multicast delegate without the need to rebuild it again.
This approach creates the most flexible and complete mechanism for resetting properties to their default values. Compared to other methods, it also improves their performance by eliminating unnecessary iterations through collecting the property’s descriptors and attributes. Unfortunately in terms of performance, it still does not favorably compare to the direct initialization method.
Initialization with Precompiled Setters
The solution with caching of delegates eliminates repetitive queries for the ResetValue
methods. Unfortunately, it still searches for the default value for each property on every call. If neither a shadowed property, nor the "ResetMyProperty
" methods are used to initialize default values, these searches could be eliminated. Initial constant value for each property can be retrieved from DefaultValue
attribute only once and stored alone with the appropriate SetValue
delegate in cache for further use.
By using lambda expressions, we can generate a custom code at run time, and create the following method:
(object o){ o.PropertySetter(constant); }
where o
is an instance of the object, PropertySetter
is a delegate to an appropriate set method, and constant
is a value retrieved from DefaultValue
attribute.
List of these expressions could be compiled and added to a multicast delegate for further use.
private static Action<object><this> setter;
public CompiledComponentModelInitialization()
{
if (null == setter)
{
ParameterExpression objectTypeParam = Expression.Parameter(typeof(object),
"this");
setter = (o) => { };
foreach (PropertyInfo prop in this.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance))
{
if (!prop.CanWrite)
continue;
DefaultValueAttribute[] attr = prop.GetCustomAttributes(
typeof(DefaultValueAttribute), false) as DefaultValueAttribute[];
if ((null == attr) || (null == attr[0]))
continue;
Expression dva = Expression.Convert(Expression.Constant(attr[0].Value),
prop.PropertyType);
Expression setExpression = Expression.Call(Expression.TypeAs(
objectTypeParam, this.GetType()),
prop.GetSetMethod(), dva);
Expression<Action<object><action><this>> setLambda =
Expression.Lambda<action><this><Action<object><action><this>>(
setExpression, objectTypeParam);
setter += setLambda.Compile();
}
}
setter(this);
}
The first time this class' instance is created, all of the public
writeable properties are iterated through, Lambda Expressions are created, compiled and added to a multicast delegate. Next time an object of this type is instantiated, these precompiled methods are simply called through that delegate.
This approach requires considerable resources and time during initialization of its first instance, but eliminates almost all of the overhead associated with the use of DefaultValue
attributes during all other instance initializations. In terms of performance, this is the only algorithm that compares very well to the direct initialization method once proxy methods are built and compiled.
Test Results
After executing this test’s application several times to eliminate random results, the following averages can be derived:
Number of Executing Cycle: | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 100 | 1000 | 10000 | 100000 | 1000000 |
Direct initialization | 973 | 8 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
Using ComponentModel | 11211 | 88 | 40 | 38 | 38 | 38 | 38 | 38 | 38 | 37 | 30 | 27 | 26 | 26 | 26 |
Using Reset Property | 4423 | 57 | 35 | 34 | 34 | 33 | 33 | 33 | 34 | 33 | 26 | 26 | 26 | 26 | 26 |
Initialization with Multicast Delegates | 8194 | 44 | 24 | 23 | 23 | 23 | 23 | 23 | 23 | 23 | 16 | 16 | 16 | 17 | 16 |
Initialization with Precompiled Setters | 18999 | 14 | 3 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
As seen from these numbers, instantiating a class for the first time takes a very long time even if nothing special is performed during initialization. After the class is instantiated at least once, the performance picks up.
The following chart demonstrates relative performance graphs for all of the methods described in this article.
*This graph does not include first cycle to improve scaling.
AOP Implementation
Aspect-Oriented Programming (AOP) is a programming paradigm in which secondary or supporting functions are isolated from the main program's business logic. Functionality provided by these initialization methods fit into this category very well.
C# provides special mechanism which allows extending any class type with custom behavior. This mechanism is called
Extension Methods. For detailed information about this technology, please visit
MSDN.
Two algorithms “Delegate cached calls to PropertyDescriptor.ReserValue()
” and “pre-compiled setters” are provided in this implementation. The methods are called ResetDefaultValues()
and ApplyDefaultValues()
respectively. In order to use these methods, follow these steps:
- Download AOP.zip, unzip and add file AOP.cs to your project.
- Bring namespace AOP into scope by adding line:
using AOP;
to your class’ source file. - Add statement
this.ResetDefaultValues();
or this.ApplyDefaultValues();
to your code whenever you need to initialize parameters with default values;
using System;
using System.Linq;
using AOP;
namespace YourNamespace
{
public class YourClass
{
public Constructor()
{
this.ApplyDefaultValues();
}
...
}
}
History
- March 16, 2010 - First publication of the article
- March 18, 2010 - After excellent suggestion by Steve Hansen, a
try
…catch
…finally
sequence in cache lookup has been replaced with Dictionary.TryGetValue
- March 19, 2010 - With help of Miss Julia Lutchenko, fixed some grammar and style
- March 25, 2010 - Fixed minor bug and added multithreading support
Any criticism, comments, bug fixes or improvements are welcome.