Click here to Skip to main content
15,884,099 members
Articles / Programming Languages / C#

DefaultValue Attribute Based Approach to Property Initializaton

Rate me:
Please Sign up or sign in to vote.
4.82/5 (34 votes)
2 Apr 2012CPOL7 min read 71.5K   864   51   9
Various approaches to implementation of property initialization with DefaultValue attributes

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:

C#
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:

C#
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:

C#
public Constructor()
{
    // Iterate through each property
    foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this))
    {
        // Set default value if DefaultValueAttribute is present
        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:

  1. There is a shadowed property for this property.
  2. There is a DefaultValueAttribute for this property.
  3. 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:

C#
public Constructor()
{
    // Iterate through each property and call ResetValue()
    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.

C#
private static Action<object><this> setter;   // Reset multicast delegate

public Constructor()
{
    // Attempt to get it from cache
    if (null == setter)
    {
        // If no initializers are added do nothing
        setter = (o) => { };

        // Go through each property and add method calls to multicast delegate
        foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this))
        {
            // Add only these which values can be reset
            if (prop.CanResetValue(this))
                setter += prop.ResetValue;
        }
    }

    // Initialize member properties
    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.

C#
// Default Value multicast delegate
private static Action<object><this> setter;  
     
public CompiledComponentModelInitialization()
{
    // Attempt to get it from cache
    if (null == setter)
    {
        ParameterExpression objectTypeParam = Expression.Parameter(typeof(object), 
                                                                   "this");
                                                                   
        // If no initializers are added do nothing
        setter = (o) => { };
        
        // Iterate through each property
        foreach (PropertyInfo prop in this.GetType().GetProperties(
            BindingFlags.Public | BindingFlags.Instance))
        {
            // Skip read only properties
            if (!prop.CanWrite)
                continue;
            
            // There are no more then one attribute of this type
            DefaultValueAttribute[] attr = prop.GetCustomAttributes( 
                     typeof(DefaultValueAttribute), false) as DefaultValueAttribute[];
                     
            // Skip properties with no DefaultValueAttribute
            if ((null == attr) || (null == attr[0]))
                continue;
                
            // Build the Lambda expression
            
            // Create constant expression with value from DefaultValue attribute 
            // and convert it into appropriate type
            Expression dva = Expression.Convert(Expression.Constant(attr[0].Value), 
                                                                  prop.PropertyType);
                                                                  
            // Create expression describing call to appropriate setter and 
            // passing it instance and value parameters
            Expression setExpression = Expression.Call(Expression.TypeAs(
                                     objectTypeParam, this.GetType()), 
                                     prop.GetSetMethod(), dva);
            
            // Create lambda expression describing proxy method which receives 
            // instance parameter and calls instance.setter( constant )
            Expression<Action<object><action><this>> setLambda = 
		Expression.Lambda<action><this><Action<object><action><this>>(
                                                     setExpression, objectTypeParam);
                                                     
            // Compile and add this action to multicast delegate
            setter += setLambda.Compile();
        }
    }
    
    // Initialize member properties
    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:123456789101001000100001000001000000
Direct initialization97382222222222222
Using ComponentModel112118840383838383838373027262626
Using Reset Property44235735343433333334332626262626
Initialization with Multicast Delegates81944424232323232323231616161716
Initialization with Precompiled Setters18999143222222222222

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.

DefaultValueInitialization
*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:

  1. Download AOP.zip, unzip and add file AOP.cs to your project.
  2. Bring namespace AOP into scope by adding line: using AOP; to your class’ source file.
  3. Add statement this.ResetDefaultValues(); or this.ApplyDefaultValues(); to your code whenever you need to initialize parameters with default values;
C#
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 trycatchfinally 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Senior Software Engineer with over 20+ years of experience in variety of technologies, development tools and programming languages.

Microsoft Certified Specialist programming in C#, JavaScript, HTML, CSS

Comments and Discussions

 
SuggestionPattern suggestion Pin
Jani Giannoudis3-Apr-12 2:41
Jani Giannoudis3-Apr-12 2:41 
GeneralRe: Pattern suggestion Pin
Eugene Sadovoi3-Apr-12 3:01
Eugene Sadovoi3-Apr-12 3:01 
SuggestionRe: Pattern suggestion Pin
Jani Giannoudis3-Apr-12 3:09
Jani Giannoudis3-Apr-12 3:09 
GeneralMy vote of 5 Pin
Andrew Chan16-Feb-11 17:01
Andrew Chan16-Feb-11 17:01 
GeneralProblems Pin
Richard Deeming25-Mar-10 6:20
mveRichard Deeming25-Mar-10 6:20 
GeneralRe: Problems Pin
Eugene Sadovoi25-Mar-10 9:42
Eugene Sadovoi25-Mar-10 9:42 
GeneralGreat article Pin
Stanislav Georgiev20-Mar-10 0:48
Stanislav Georgiev20-Mar-10 0:48 
GeneralSuggestion PinPopular
Steve Hansen18-Mar-10 5:43
Steve Hansen18-Mar-10 5:43 
GeneralRe: Suggestion [modified] Pin
Eugene Sadovoi18-Mar-10 6:23
Eugene Sadovoi18-Mar-10 6:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.