Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to Configure Default Values in .NET Classes.

0.00/5 (No votes)
24 Feb 2002 1  
This article describes how to setup default values of the value type fields in the .NET class using the Custom Attribute from the Application Assembly or Config file.

Contents

Introduction

This article describes how to setup default values of the value type fields in the .NET class using the Custom Attribute from the Application Assembly or Config file. The concept is based on attributing the fields with a specified default value. These default values are persisted in the assembly metadata during the compiling time. Using the "generic loader" function during the run-time, the attributed fields can be initialized by these Default Values. The best understanding this concept is to show a usage of the FieldAttribute in the example:

Usage

The following code snippet shows a usage of the FieldAttribute for different types of the fields in the class:

namespace ClassSamples 
{
   public enum Day { Sunday, Monday, Tuesday, Wednesday, Thursday, 
         Friday, Saturday }
   public class ClassA 
   { 
      [Field(DefaultValue = 1.23E+28)]
      public decimal d;

      [Field("This is a string A")]
      public string s;

      [Field(777.789)]
      public double db;

      [Field("F6B67807-2AC7-4ba6-BCA7-75D4B7668EC4")]
      public Guid guid;

      [Field(Day.Monday)]
      public Day day;

      [Field("ClassA")]
      public object obj;

      public ClassA() 
      {
         //DefaultValues.Set(this);

      }
   }

   public class ClassB : ClassA 
   { 
      [Field(DefaultValue = 123456789)]
      public int i;

      [Field("This is a string B")]
      public new string s;

      [Field(10, Desc="Create a string array[10]")]
      public string[] sarr; 

      [Field(0x0a)]
      public byte hex;

      [Field(false)]
      public bool b;

      [Field]
      public new Guid guid;

      public ClassB() 
      {
        //DefaultValues.Set(this); 

      }
   }

   public class ClassC : ClassB 
   {
      [Field("This is a string C")]
      public new string s;

      [Field(1)]
      public sbyte sb;

      [Field(1234.5E+20)]
      public float f;

      public ClassC() 
      {
         DefaultValues.Set(this);
      }
   }
}

Each public field of the class can be attributed using the custom attribute FieldAttribute with the specified value and/or description. Note that the value has to be in the format of the field type, for instance; the Guid type field is using a string value type, the array field is using this integer value as a size of the array and so on. In the case of the wrong (or missing) value specified by this attribute, the original value of the field will not be changed.

The value of the FieldAttribute are persisted into the assembly metadata. During the run-time, we need a some "custom attribute loader" function to retrieve for particular attributed field its value from the storage (either from the assembly metadata or config file). This function is shown in the above samples as DefaultValues.Set(this). - see more details at Class DefaultValues. Usually place for this function is in the class ctor, but it can be used any place within the same assembly. The following code snippet shows this implementation:

namespace DefaultValuesConsole
{
   class Class1
   {
      static void Main(string[] args)
      { int tc = 0;

        ClassA a = new ClassA();     // setup by CLR

        ClassB b = new ClassB();     // setup by CLR

        ClassC c = new ClassC();     // setup from ctor


    // Dump all fields of the ClassC

        DefaultValues.Dump(c, "c", DumpOption.Console);
        Console.WriteLine("press any key to continue this test");
        Console.ReadLine();
        //

        // setup ClassB from the FieldAttribute values

        b = DefaultValues.Set(new ClassB(), 
                              DefaultValuesOption.FromAssembly) as ClassB; 
        DefaultValues.Dump(b, "b", DumpOption.Console);
        Console.WriteLine("press any key to continue this test");
        Console.ReadLine();
        //

        while(true) 
        {
           // setup ClassC from the config file

           tc = Environment.TickCount; 
           DefaultValues.Set(c, DefaultValuesOption.FromConfigFile);
           tc = Environment.TickCount - tc; 
           DefaultValues.Dump(c, "c", DumpOption.Console);
           Console.WriteLine("time={0}[ms], press any key to continue " + 
                             "this test", tc);
           Console.ReadLine(); // press Ctrl-C to exit this test

        }
     }
   }
}

The above code snippet is a test console program where classes A, B and C have been initiated and setup their fields from the assembly default values respectively from the config default values. The program shows a different ways how to do it - inside or outside of the class ctor. The function DefaultValues.Set has a option to select a source of the default values between the assembly or config file. The default option is the assembly metadata source.

Using the config file to store the default values of the class fields needs to follow the format as it shown in the following code snippet. Its configSection node contains a DefaultValues section group with a specified sections of the classes. Each class section includes a name of the field and its default value.

<configuration>
 <configSections>
  <sectionGroup name="DefaultValues">
   <section name="ClassSamples.ClassA" 
            type="System.Configuration.NameValueSectionHandler,System" />
   <section name="ClassSamples.ClassB" 
            type="System.Configuration.NameValueSectionHandler,System" />
   <section name="ClassSamples.ClassC" 
            type="System.Configuration.NameValueSectionHandler,System" />
  </sectionGroup>
 </configSections>

 <DefaultValues>
  <ClassSamples.ClassA>
   <add key="d" value="199998888888999999.23456789" />
   <add key="s" value="This is a string A - Config" />
   <add key="db" value="1111.2222" />
   <add key="guid" value="00000000-2AC7-4ba6-BCA7-75D4B7668EC4" />
   <add key="day" value="Tuesday" />
   <add key="obj" value="ClassB" />
  </ClassSamples.ClassA>

  <ClassSamples.ClassB>
   <add key="i" value="1" />
   <add key="sarr" value="1000" />
   <add key="hex" value="128" />
   <add key="guid" value="11111111-2222-3333-4444-555555555555" />
   <add key="s" value="This is a string B - Config" />
   <add key="b" value="true" />
  </ClassSamples.ClassB>

  <ClassSamples.ClassC>
   <add key="s" value="This is a string C - Config" />
   <add key="sb" value="100" />
   <add key="f" value="1234567890.0123456789" />
  </ClassSamples.ClassC>
 </DefaultValues> 
</configuration>

Implementation

The solution has been divided into two parts:

  • FieldAttribute
  • DefaultValues

Let's look at about them for more details:

FieldAttribute

This is the Custom Attribute class for the class field usage. As you can see in the following code snippet its implementation is very simply and is based on the values _DefaultValue and _Desc. Both are smart fields and they can be initialized using the set or ctor arguments.

// the field attribute

[AttributeUsage(AttributeTargets.Field)]
public class FieldAttribute : Attribute
{
   private object _DefaultValue;
   private string _Desc = string.Empty;
   public object DefaultValue 
   {
      get{ return _DefaultValue; }
      set{ _DefaultValue = value; }
   }
   public string Desc 
   {
      get{ return _Desc; }
      set{ _Desc = value; }
   }
   public FieldAttribute() {}
   public FieldAttribute(object defaultValue) 
   {
      _DefaultValue = defaultValue;
   }
   public FieldAttribute(object defaultValue, string desc) 
   {
      _DefaultValue = defaultValue;
      _Desc = desc;
   }
}

Class DefaultValues

This "magic" class has been designed and implemented for the FieldAttribute purpose only. Its responsibility is to initialize attributed field in the class with the Default value from the specified storage such as an application assembly or config file. Both functions have a generic design pattern based on using the Reflection technique, which is a integral part of the .NET Framework, to retrieve a specified type from the assembly metadata.

The FieldAttribute ctor is called by invoking the GetCustomAttributes(true) for each attributed field. During this time its fields such as _DefaultValue and _Desc are set up. Selecting a properly target type conversion, the attributed field can be updated by the Reflection.FieldInfo.SetValue method.

There is a function Dump in this class to display values of the all fields for a specified class on the console or trace output stream. This function has a test purpose only.

// util class for updating values either from assembly or config file

public enum DefaultValuesOption { FromAssembly, FromConfigFile }
public enum DumpOption { Console, Trace }

public class DefaultValues 
{
   static public object Set(object parent) 
   { 
      return Set(parent, DefaultValuesOption.FromAssembly); 
   }
 
   static public object Set(object parent, DefaultValuesOption option) 
   { 
      if(parent == null)
         return null;

      Type type = parent.GetType();

      foreach(FieldInfo fi in type.GetFields()) 
      {
         foreach(Attribute attr in fi.GetCustomAttributes(true)) 
         {
            if(attr is FieldAttribute) 
            { 
               object DefaultValue = null;

               if(option == DefaultValuesOption.FromConfigFile) 
               {
                  string cfgSectionName = "DefaultValues/"
                                       + fi.DeclaringType.FullName;
                  NameValueCollection cfgClass = 
            (NameValueCollection)ConfigurationSettings.GetConfig(
                  cfgSectionName);
                  DefaultValue = cfgClass[fi.Name];
               }
               else 
               {
                  DefaultValue = (attr as FieldAttribute).DefaultValue;
               }

               try
               {
                  if(fi.FieldType == typeof(System.Guid)) 
                  {
                     fi.SetValue(parent, new Guid(DefaultValue.ToString()));
                  }
                  else
                  if(fi.FieldType == typeof(decimal)) 
                  {
                     if(DefaultValue.GetType() == typeof(string)) 
                        fi.SetValue(parent, 
                          decimal.Parse(DefaultValue.ToString()));
                     else
                        fi.SetValue(parent, Convert.ToDecimal(DefaultValue));
                  }
                  else
                  if(fi.FieldType.IsEnum == true) 
                  {
                     object en = Enum.Parse(fi.FieldType, 
                         DefaultValue.ToString());
                     fi.SetValue(parent, en);
                  }
                  else
                  if(fi.FieldType.IsArray == true) 
                  {
                     object arr = Activator.CreateInstance(fi.FieldType, 
                            new object[]{Convert.ToInt32(DefaultValue)});
                     fi.SetValue(parent, arr ); 
                  }
                  else 
                  if(fi.FieldType != DefaultValue.GetType())
                  {
                     fi.SetValue(parent, Convert.ChangeType(DefaultValue, 
                        fi.FieldType));
                  }
                  else 
                  {
                     fi.SetValue(parent, DefaultValue);
                  }
               }
               catch(Exception ex)
               {
                  Trace.WriteLine(string.Format(
                     "Message:{0} [{1}.{2} = {3}]", 
                      ex.Message, type.FullName, fi.Name, DefaultValue));
               }
            }
         }
      }
      return parent;
   }

   // dumping a field's value

   static public void Dump(object parent, string prompt) 
   { 
       Dump(parent, prompt, DumpOption.Trace); 
   }

   static public void Dump(object parent, string prompt, DumpOption option) 
   {
      if(parent == null)
         return;

      Type type = parent.GetType();
      string strFieldInfo = string.Empty;

      foreach(FieldInfo fi in type.GetFields()) 
      {
         string strClassName = fi.DeclaringType.FullName;

         if(fi.FieldType.IsArray == true) 
         {
            string size = (fi.GetValue(parent) == null) ? "null" : 
                            ((Array)fi.GetValue(parent)).Length.ToString();
            strFieldInfo = string.Format("{0}.[{1}.{2} = {3}].{4}.size={5}", 
                        prompt, strClassName, fi.Name, fi.GetValue(parent), 
                        fi.FieldType.FullName, size);
         }
         else
         {
            strFieldInfo = string.Format("{0}.[{1}.{2} = {3}].{4}", 
                       prompt, strClassName, fi.Name, fi.GetValue(parent), 
                       fi.FieldType.FullName);
         }
         if(option == DumpOption.Console)
            System.Console.WriteLine(strFieldInfo);
         else
            System.Diagnostics.Trace.WriteLine(strFieldInfo);
      }
   }
}

Test

The FieldAttribute can be tested using the console program DefaultValuesConsole included this package. This is a very simple program to initiate few classes with the default values and then displaying their state. The following screen snap shows the first step of this test.

Test program

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here