65.9K
CodeProject 正在变化。 阅读更多。
Home

如何在 .NET 类中配置默认值。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (5投票s)

2001 年 11 月 25 日

3分钟阅读

viewsIcon

94510

downloadIcon

519

本文介绍了如何使用应用程序程序集或配置文件中的自定义特性来设置 .NET 类中值类型字段的默认值。

目录

引言

本文介绍了如何使用应用程序程序集或配置文件中的 自定义 特性来设置 .NET 类中值类型字段的默认值。该概念基于为字段指定一个默认值。这些默认值在编译时会持久化到程序集元数据中。在运行时,使用“通用加载器”函数,可以根据这些默认值初始化已添加特性的字段。要最好地理解这个概念,需要查看示例中 FieldAttribute 的用法。

用法

下面的代码片段展示了在类中对不同类型的字段使用 FieldAttribute 的用法。

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);
      }
   }
}

类的每个公共字段都可以使用自定义特性 FieldAttribute 进行标记,并指定相应的值和/或描述。请注意,值必须符合字段类型的格式;例如,Guid 类型字段使用字符串值类型,数组字段将其整数值用作数组的大小,依此类推。如果此特性的值错误(或缺失),字段的原始值将不会改变。

FieldAttribute 的值会持久化到程序集元数据中。在运行时,我们需要一个“自定义特性加载器”函数来从存储(无论是程序集元数据还是配置文件)中检索特定已标记字段的值。此函数在上面的示例中显示为 DefaultValues.Set(this) - 更多详细信息请参阅 Class DefaultValues。通常将此函数放在类的构造函数中,但也可以在同一程序集内的任何位置使用。下面的代码片段展示了其实现。

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
        }
     }
   }
}

上面的代码片段是一个测试控制台程序,其中类 A、B 和 C 已初始化,并分别从程序集默认值和配置文件默认值设置了它们的字段。该程序展示了在类构造函数内部或外部执行此操作的不同方法。DefaultValues.Set 函数可以选择在程序集或配置文件之间作为默认值来源。默认选项是程序集元数据源。

使用配置文件存储类字段的默认值时,需要遵循以下代码片段所示的格式。其 configSection 节点包含一个 DefaultValues 部分组,其中包含指定的类部分。每个类部分都包括字段名称及其默认值。

<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>

实现

解决方案分为两部分。

  • FieldAttribute
  • DefaultValues

让我们详细看看它们。

FieldAttribute

这是用于类字段使用的自定义特性类。从下面的代码片段可以看出,其实现非常简单,基于 _DefaultValue_Desc 的值。两者都是智能字段,可以使用 set 或 ctor 参数进行初始化。

// 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

这个“神奇”类仅为 FieldAttribute 的目的而设计和实现。它的责任是使用来自指定存储(如应用程序程序集或配置文件)的默认值来初始化类中已标记的字段。这两个函数都基于使用反射技术(.NET Framework 的一个组成部分)的通用设计模式,以从程序集元数据中检索指定的类型。

通过为每个已标记字段调用 GetCustomAttributes(true) 来调用 FieldAttribute 的构造函数。此时,其字段(如 _DefaultValue_Desc)将被设置。通过选择合适的目標类型转换,可以使用 Reflection.FieldInfo.SetValue 方法更新已标记的字段。

此类中有一个 Dump 函数,用于在控制台或跟踪输出流上显示指定类的所有字段的值。此函数仅用于测试目的。

// 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);
      }
   }
}

测试

FieldAttribute 可以使用此包中包含的控制台程序 DefaultValuesConsole 进行测试。这是一个非常简单的程序,用于初始化几个类并设置它们的默认值,然后显示它们的状态。下面的屏幕截图显示了此测试的第一步。

Test program

© . All rights reserved.