解码.NET 2.0配置之谜(二)

此文是解码.NET 2.0配置之谜(一)的延续,也是揭秘.NET 2.0配置系列中的一部分,点此查看此系列文章。Let's continue!

2、保持类型安全

.NET平台的出色的地方是其严格的类型安全。此功能有助于书写安全、可靠的代码。任何应用程序中最脆弱的部分是那些访问外部资源的部分,如配置。值得庆幸的是,.NET 2.0的配置框架包括一些功能,帮助你确保你的配置数据是类型安全的。最初,所有配置数据理解为一个字符串,且在反序列化时必须转化为合适的类型才能使用。.NET 2.0的配置框架自动做了大部分这样的工作,但是有些时候,默认的转换是不够的。配置类型转换器可以用来处理自定义转换,当与验证器一起使用,可以确保你的配置数据类型安全可靠。

2.1、使用预制转换器

当编写自定义配置节,类型转换通常由.NET 2.0配置框架自动完成。有一些特殊的实例自动转换是不够的,例如当一个整数值需要是任意整数或无限,或当一个逗号分割的字符串需要被转换为一个集合。许多预制转换器的存在,并且他们可以声明地或命令地应用于自定义配置类:

转换器类型
  1. CommaDelimitedStringCollectionConverter - Converts a comma-delimited value to/from a CommaDelimitedStringCollection
  2. GenericEnumConverter - Converts between a string and an enumerated type
  3. InfiniteIntConverter - Converts between a string and the standard infinite or integer value
  4. InfiniteTimeSpanConverter - Converts between a string and the standard infinite TimeSpan value
  5. TimeSpanMinutesConverter - Converts to and from a time span expressed in minutes
  6. TimeSpanMinutesOrInfiniteConverter - Converts to and from a time span expressed in minutes, or infinite
  7. TimeSpanSecondsConverter - Converts to and from a time span expressed in seconds
  8. TimeSpanSecondsOrInfiniteConverter - Converts to and from a time span expressed in seconds, or infinite
  9. TypeNameConverter - Converts between a Type object and the string representation of that type
  10. WhiteSpaceTrimStringConverter - Converts a string to its canonical format (white space trimmed from front and back)

同样,这些都是不言而明的,需要一点解释,但由于后代的缘故,我将提供一个例子。有一点需要注意的是,枚举自动处理和良好的被框架通过使用GenericEnumConverter,因此手动使用它似乎没有任何价值。在应用配置类型转换器的自定义配置类,你可以使用标准的TypeConverterAttribute

  
  
  
  
  1. public class TypeSafeExampleSection: ConfigurationSection  
  2. {  
  3.     #region Constructor  
  4.     static TypeSafeExampleSection()  
  5.     {  
  6.  
  7.         s_propMyInt = new ConfigurationProperty(  
  8.             "myInt",  
  9.             typeof(int),  
  10.             "Infinite",  
  11.             new InfiniteIntConverter(),  
  12.             new IntegerValidator(-10, 10),  
  13.             ConfigurationPropertyOptions.IsRequired  
  14.         );  
  15.  
  16.         s_properties = new ConfigurationPropertyCollection();  
  17.         s_properties.Add(s_propMyInt);  
  18.     }  
  19.     #endregion  
  20.  
  21.     #region Fields  
  22.     private static ConfigurationPropertyCollection s_properties;  
  23.     private static ConfigurationProperty s_propMyInt;  
  24.     #endregion  
  25.  
  26.     #region Properties  
  27.     [ConfigurationProperty("myInt", DefaultValue="Infinite", IsRequired=true)]  
  28.     [IntegerValidator(-10, 10)]  
  29.     [TypeConverter(typeof(InfiniteIntConverter)]  
  30.     public int MyInt  
  31.     {  
  32.         get { return (int)base[s_propMyInt]; }  
  33.     }  
  34.     #endregion  

2.2、编写自己的转换器

类型转化器的真值显示,当你进入一个方案下运行,XML/text表示的数据没有直接的关系且.NET类和结构存储数据。考虑这个简单的结构:

  
  
  
  
  1. struct MyStruct  
  2. {  
  3.     public int Length;  
  4.     public int Width;  
  5.     public int Height;  
  6.  
  7.     public double X;  
  8.     public double Y;  
  9.     public double Z;  
  10.  
  11.     public override string ToString()  
  12.     {  
  13.         return "L: " + Length + ", W: " + Width + ", H: " + Height +   
  14.                ", X: " + X + ", Y: " + Y + ", Z: " + Z;  
  15.     }  

在配置文件中使用这样的结构没有使用使用int、timespan或string那样直接简单。一个通用,可编辑的方式表示这个结构是需要的,因此我们的.config文件是可以手工修改的(vs. 通过配置程序)。假设我们选择了这样的格式字符串表示:

  
  
  
  
  1. L: 20, W: 30, H: 10, X: 1.5, Y: 1.0, Z: 7.3 

再假定,我们既不关心每对的顺序,也不关心每个构成部分之间的大写或空格。假设所有六个构成部分都是需要的,而且他们都不能被排除在外。现在,我们可以写一个类型转换器转换字符串表示和我们的MyStruct结构。注意,对于ConvertTo方法,我们简单的直接转换为一个字符串,没有验证类型参数要求是一个字符串。我们呢还假设ConvertFrom方法始终接收一个字符串值作为数据参数。由于这是一个配置转换器,这些假设情况总是安全的。

  
  
  
  
  1. public sealed class MyStructConverter: ConfigurationConverterBase  
  2. {  
  3.     public MyStructConverter() { }  
  4.  
  5.     /// <summary>Converts a string to a MyStruct.</summary>  
  6.     /// <returns>A new MyStruct value.</returns>  
  7.     /// The <see   
  8.     /// cref="T:System.ComponentModel.ITypeDescriptorContext">  
  9.     /// </see> object used for type conversions.  
  10.     /// The <see   
  11.     /// cref="T:System.Globalization.CultureInfo">  
  12.     /// </see> object used during conversion.  
  13.     /// The <see cref="T:System.String">  
  14.     /// </see> object to convert.  
  15.     public override object ConvertFrom(ITypeDescriptorContext ctx,   
  16.                                        CultureInfo ci, object data)  
  17.     {  
  18.         string dataStr = ((string)data).ToLower();  
  19.         string[] values = dataStr.Split(',');  
  20.     if (values.Length == 6)  
  21.         {  
  22.             try 
  23.             {  
  24.                 MyStruct myStruct = new MyStruct();  
  25.                 foreach (string value in values)  
  26.                 {  
  27.                     string[] varval = value.Split(':');  
  28.                     switch (varval[0])  
  29.                     {  
  30.                         case "l":   
  31.                           myStruct.Length = Convert.ToInt32(varval[1]); break;  
  32.                         case "w":   
  33.                           myStruct.Width = Convert.ToInt32(varval[1]); break;  
  34.                         case "h":   
  35.                           myStruct.Height = Convert.ToInt32(varval[1]); break;  
  36.                         case "x":   
  37.                           myStruct.X = Convert.ToDouble(varval[1]); break;  
  38.                         case "y":   
  39.                           myStruct.Y = Convert.ToDouble(varval[1]); break;  
  40.                         case "z":   
  41.                           myStruct.Z = Convert.ToDouble(varval[1]); break;  
  42.                     }  
  43.                 }  
  44.                 return myStruct;  
  45.             }  
  46.             catch 
  47.             {  
  48.                 throw new ArgumentException("The string contained invalid data.");  
  49.             }  
  50.         }  
  51.  
  52.         throw new ArgumentException("The string did not contain all six, " +   
  53.                                     "or contained more than six, values.");  
  54.     }  
  55.  
  56.     /// <summary>Converts a MyStruct to a string value.</summary>  
  57.     /// <returns>The string representing the value   
  58.     ///           parameter.</returns>  
  59.     /// The <see   
  60.     /// cref="T:System.ComponentModel.ITypeDescriptorContext">  
  61.     /// </see> object used for type conversions.  
  62.     /// The <see   
  63.     /// cref="T:System.Globalization.CultureInfo">  
  64.     /// </see> object used during conversion.  
  65.     /// The <see cref="T:System.String">  
  66.     /// </see> object to convert.  
  67.     /// The type to convert to.  
  68.     public override object ConvertTo(ITypeDescriptorContext ctx,   
  69.            CultureInfo ci, object value, Type type)  
  70.     {  
  71.         return value.ToString();  
  72.     }  

3、关注性能

低性能可能是任何应用程序的头号杀手之一,往往是应用程序最难进行调整的方面。就像开发者环境的梦一样很快就会淹没在高流通量、高声产环境中。对许多应用程序优化往往是长时间运行且正运行中的任务。值的庆幸的是,优化你的自定义配置类是一个非常简单的事情,并几乎没有任何不优化他们的额外工作。在本文和前篇文章中,我已经编写简单的配置类例子,用两种方法:声明地用属性(attributes),显示地使用静态变量和静态构造器。显示的方法是优化的方法,但是他的优化不明显,除非你深入挖掘.NET 2.0的配置框架源码。

快速简单的方式编写一个配置类是使用属性(attributes),应用ConfigurationPropertyAttributeTypeConverterAttribute和各种验证器属性(attributes)到简单类的公有属性(properties)。这提供了非常快速的开发,但是长远来说性能是会很差。当.NET 2.0配置框架加载这样一个配置类,大量的反射和对象构造必须随之创建必要的ConfigurationProperty对象及其相关的验证器和类型转换器。当自定义配置类的属性(properties)集合被访问时,将这样做,通过下面的代码片段可以看到:

  
  
  
  
  1. /// <summary>Gets the collection of properties.</summary>  
  2. /// <returns>The <see   
  3. /// cref="T:System.Configuration.ConfigurationPropertyCollection">  
  4. /// </see> collection of properties for the element.</returns>  
  5. protected virtual ConfigurationPropertyCollection Properties  
  6. {  
  7.       get 
  8.       {  
  9.             ConfigurationPropertyCollection collection1 = null;  
  10.             if (ConfigurationElement.PropertiesFromType(base.GetType(),   
  11.                                                         out collection1))  
  12.             {  
  13.                   ConfigurationElement.ApplyInstanceAttributes(this);  
  14.                   ConfigurationElement.ApplyValidatorsRecursive(this);  
  15.             }  
  16.             return collection1;  
  17.       }  

这是默认的ConfigurationElement.Properties属性。任何时候他被访问,static PropertiesFromType(Type, out ConfigurationPropertyCollection)将被调用,它启动一个过程,发现并建立在你的自定义配置类中包装的指定类的任何ConfigurationProperty对象,型。注意,为指定类型建立所有的ConfigurationProperty对象,不只是你要求的那个。一旦创建一个指定配置的属性(property)集合,它将被缓存,改进随后调用的性能。

  
  
  
  
  1. private static bool PropertiesFromType(Type type,   
  2.         out ConfigurationPropertyCollection result)  
  3. {  
  4.       ConfigurationPropertyCollection collection1 =   
  5.        (ConfigurationPropertyCollection)   
  6.         ConfigurationElement.s_propertyBags[type];  
  7.       result = null;  
  8.       bool flag1 = false;  
  9.       if (collection1 == null)  
  10.       {  
  11.             lock (ConfigurationElement.s_propertyBags.SyncRoot)  
  12.             {  
  13.                   collection1 = (ConfigurationPropertyCollection)   
  14.                                  ConfigurationElement.s_propertyBags[type];  
  15.                   if (collection1 == null)  
  16.                   {  
  17.                         collection1 =   
  18.                          ConfigurationElement.CreatePropertyBagFromType(type);  
  19.                         ConfigurationElement.s_propertyBags[type] =   
  20.                                              collection1;  
  21.                         flag1 = true;  
  22.                   }  
  23.             }  
  24.       }  
  25.       result = collection1;  
  26.       return flag1;  

同样值得注意的是,PropertiesFromType函数是静态的,按照微软的准则,所有的静态方法必须是类型安全的。在一个配置类上对任何属性(property)的多个要求都将通过一个锁被序列化,挂起所有在竞争的线程直到所有给定类型已经创建和缓存ConfigurationProperty对象。如果ConfigurationPropertyCollection没有被创建,ConfigurationProperty对象被发现并建立通过调用static CreatePropertyBagFromType(Type)。此函数将启动一个相当漫长的过程,招致多次反射请求和属性表查找创建每个ConfigurationProperty对象。

一旦你的ConfigurationProperty对象以创建,并放置在相应的ConfigurationPropertyCollection,为了随后的调用他们将被缓存。但是,如果根本的.config文件改变了,属性(properties)必须重新加载,导致再次创建的代价。他可能完全绕过过程,通过显示地定义和创建你的ConfigurationProperty对象,用静态构造器方法。理解为什么这能提高性能的技巧是理解什么重写Properties property做了什么。比较下面的代码和原始属性的代码:

  
  
  
  
  1. public override ConfigurationPropertyCollection Properties  
  2. {  
  3.     get 
  4.     {  
  5.         return s_properties;  
  6.     }  

在重写的属性(property),我们完全绕过了基本的代码,返回我们准备的属性(properties)集合。我们的属性(properties)集合没有导致任何反射代价原因在于我们显示地(明确地)定义每个ConfigurationProperty,以及相应的验证器和类型转换器。努力地显示地执行创建一个自定义配置类与命令地执行的差异是如此的小,没有理由不这样做。显示地编写配置类也有利于配置重新载入,而配置可用于应用程序更快。(注意:在重新载入配置时,有可能引发随后的第一个加载,而仍在进行中。当这种情况发生时,其他依赖这个的代码会崩溃或行为古怪。缩短重新装载的时间有助于减少这种影响,并减少编写特殊情况下的反应。)

4、最佳配置实践

最大限度地用.NET 2.0配置框架一般是指当编写任何代码或为任何其他目的使用XML时遵循同样的最佳做法。最佳做法就是最佳做法,所以不要吝啬了只因为它的配置,你所处理的。一些最佳实践,当我为编我的.NET 应用程序写配置是使用的如下:

  • 切勿使用超过你的需要。如果你的要求非常简单,使用 <appSettings> 节。如果你需要更多的类型安全,但并不需要完整的自定义配置节,请尝试使用VS2005的项目设置功能。
  • 不要试图保持它太“简单”。一个项目开始时可以用简单的要求,但随着时间的推移,需求复杂性通常会增加。管理和维护数百或千的 <appSettings> 总是一个乏味的任务。合并成结构化配置节将允许你封装、模块化,并简化你的配置。将一些自定义配置节放在一起花的时间将很快赚回来,减少了配置维护的代价。
  • 代码重用。尽量保留在你的脑海里,“重用,重用,重用”。许多应用程序经常需要同一段配置。在设计和实施你的配置节,试图使他们尽可能地通用,并尽可能最大限度在未来重用。不要编写一个自定义配置集合,如果一个预制的可以重用。有个,在本系列的第一篇文中列出。
  • 总是写高性能的配置代码。很少有简单的模式,可以应用于自定义配置节以最大化提高性能,如3、关注性能中所描述的。从这些模式的性能提升显著,而增加的复杂性是很微不足道的。千万不要说“我以后将修复性能”,到那时已经晚了。;)

这篇译文已经翻译完了,后续将翻译——破解.NET 2.0配置之谜。如果您觉得好,推荐下能让更多的人看到。

声明:此文是译文,实因水平有限,若有翻译不当之处请不吝指出,以免此译文误导他人,在此谢过!

英文原文:Jon Rista,Decoding the Mysteries of .NET 2.0 Configuration

你可能感兴趣的:(.net,配置,翻译,解码,休闲,2.0)