天天看點

解碼.NET 2.0配置之謎(二)

.NET平台的出色的地方是其嚴格的類型安全。此功能有助于書寫安全、可靠的代碼。任何應用程式中最脆弱的部分是那些通路外部資源的部分,如配置。值得慶幸的是,.NET 2.0的配置架構包括一些功能,幫助你確定你的配置資料是類型安全的。最初,所有配置資料了解為一個字元串,且在反序列化時必須轉化為合适的類型才能使用。.NET 2.0的配置架構自動做了大部分這樣的工作,但是有些時候,預設的轉換是不夠的。配置類型轉換器可以用來處理自定義轉換,當與驗證器一起使用,可以確定你的配置資料類型安全可靠。

當編寫自定義配置節,類型轉換通常由.NET 2.0配置架構自動完成。有一些特殊的執行個體自動轉換是不夠的,例如當一個整數值需要是任意整數或無限,或當一個逗号分割的字元串需要被轉換為一個集合。許多預制轉換器的存在,并且他們可以聲明地或指令地應用于自定義配置類:

<code>CommaDelimitedStringCollectionConverter</code> - Converts a comma-delimited value to/from a <code>CommaDelimitedStringCollection</code>

<code>GenericEnumConverter</code> - Converts between a string and an enumerated type

<code>InfiniteIntConverter</code> - Converts between a string and the standard infinite or integer value

<code>InfiniteTimeSpanConverter</code> - Converts between a string and the standard infinite <code>TimeSpan</code> value

<code>TimeSpanMinutesConverter</code> - Converts to and from a time span expressed in minutes

<code>TimeSpanMinutesOrInfiniteConverter</code> - Converts to and from a time span expressed in minutes, or infinite

<code>TimeSpanSecondsConverter</code> - Converts to and from a time span expressed in seconds

<code>TimeSpanSecondsOrInfiniteConverter</code> - Converts to and from a time span expressed in seconds, or infinite

<code>TypeNameConverter</code> - Converts between a <code>Type</code> object and the string representation of that type

<code>WhiteSpaceTrimStringConverter</code> - Converts a string to its canonical format (white space trimmed from front and back)

同樣,這些都是不言而明的,需要一點解釋,但由于後代的緣故,我将提供一個例子。有一點需要注意的是,枚舉自動處理和良好的被架構通過使用<code>GenericEnumConverter</code>,是以手動使用它似乎沒有任何價值。在應用配置類型轉換器的自定義配置類,你可以使用标準的<code>TypeConverterAttribute</code>。

public class TypeSafeExampleSection: ConfigurationSection  

{  

    #region Constructor  

    static TypeSafeExampleSection()  

    {  

        s_propMyInt = new ConfigurationProperty(  

            "myInt",  

            typeof(int),  

            "Infinite",  

            new InfiniteIntConverter(),  

            new IntegerValidator(-10, 10),  

            ConfigurationPropertyOptions.IsRequired  

        );  

        s_properties = new ConfigurationPropertyCollection();  

        s_properties.Add(s_propMyInt);  

    }  

    #endregion  

    #region Fields  

    private static ConfigurationPropertyCollection s_properties;  

    private static ConfigurationProperty s_propMyInt;  

    #region Properties  

    [ConfigurationProperty("myInt", DefaultValue="Infinite", IsRequired=true)]  

    [IntegerValidator(-10, 10)]  

    [TypeConverter(typeof(InfiniteIntConverter)]  

    public int MyInt  

        get { return (int)base[s_propMyInt]; }  

類型轉化器的真值顯示,當你進入一個方案下運作,XML/text表示的資料沒有直接的關系且.NET類和結構存儲資料。考慮這個簡單的結構:

struct MyStruct  

    public int Length;  

    public int Width;  

    public int Height;  

    public double X;  

    public double Y;  

    public double Z;  

    public override string ToString()  

        return "L: " + Length + ", W: " + Width + ", H: " + Height +   

               ", X: " + X + ", Y: " + Y + ", Z: " + Z;  

在配置檔案中使用這樣的結構沒有使用使用int、timespan或string那樣直接簡單。一個通用,可編輯的方式表示這個結構是需要的,是以我們的.config檔案是可以手工修改的(vs. 通過配置程式)。假設我們選擇了這樣的格式字元串表示:

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

再假定,我們既不關心每對的順序,也不關心每個構成部分之間的大寫或空格。假設所有六個構成部分都是需要的,而且他們都不能被排除在外。現在,我們可以寫一個類型轉換器轉換字元串表示和我們的<code>MyStruct</code>結構。注意,對于<code>ConvertTo方法,我們簡單的直接轉換為一個字元串,沒有驗證類型參數要求是一個字元串。我們呢還假設<code>ConvertFrom</code>方法始終接收一個字元串值作為資料參數。由于這是一個配置轉換器,這些假設情況總是安全的。</code>

public sealed class MyStructConverter: ConfigurationConverterBase  

    public MyStructConverter() { }  

    /// &lt;summary&gt;Converts a string to a MyStruct.&lt;/summary&gt;  

    /// &lt;returns&gt;A new MyStruct value.&lt;/returns&gt;  

    /// The &lt;see   

    /// cref="T:System.ComponentModel.ITypeDescriptorContext"&gt;  

    /// &lt;/see&gt; object used for type conversions.  

    /// cref="T:System.Globalization.CultureInfo"&gt;  

    /// &lt;/see&gt; object used during conversion.  

    /// The &lt;see cref="T:System.String"&gt;  

    /// &lt;/see&gt; object to convert.  

    public override object ConvertFrom(ITypeDescriptorContext ctx,   

                                       CultureInfo ci, object data)  

        string dataStr = ((string)data).ToLower();  

        string[] values = dataStr.Split(',');  

    if (values.Length == 6)  

        {  

            try 

            {  

                MyStruct myStruct = new MyStruct();  

                foreach (string value in values)  

                {  

                    string[] varval = value.Split(':');  

                    switch (varval[0])  

                    {  

                        case "l":   

                          myStruct.Length = Convert.ToInt32(varval[1]); break;  

                        case "w":   

                          myStruct.Width = Convert.ToInt32(varval[1]); break;  

                        case "h":   

                          myStruct.Height = Convert.ToInt32(varval[1]); break;  

                        case "x":   

                          myStruct.X = Convert.ToDouble(varval[1]); break;  

                        case "y":   

                          myStruct.Y = Convert.ToDouble(varval[1]); break;  

                        case "z":   

                          myStruct.Z = Convert.ToDouble(varval[1]); break;  

                    }  

                }  

                return myStruct;  

            }  

            catch 

                throw new ArgumentException("The string contained invalid data.");  

        }  

        throw new ArgumentException("The string did not contain all six, " +   

                                    "or contained more than six, values.");  

    /// &lt;summary&gt;Converts a MyStruct to a string value.&lt;/summary&gt;  

    /// &lt;returns&gt;The string representing the value   

    ///           parameter.&lt;/returns&gt;  

    /// The type to convert to.  

    public override object ConvertTo(ITypeDescriptorContext ctx,   

           CultureInfo ci, object value, Type type)  

        return value.ToString();  

低性能可能是任何應用程式的頭号殺手之一,往往是應用程式最難進行調整的方面。就像開發者環境的夢一樣很快就會淹沒在高流通量、高聲産環境中。對許多應用程式優化往往是長時間運作且正運作中的任務。值的慶幸的是,優化你的自定義配置類是一個非常簡單的事情,并幾乎沒有任何不優化他們的額外工作。在本文和前篇文章中,我已經編寫簡單的配置類例子,用兩種方法:聲明地用屬性(attributes),顯示地使用靜态變量和靜态構造器。顯示的方法是優化的方法,但是他的優化不明顯,除非你深入挖掘.NET 2.0的配置架構源碼。

快速簡單的方式編寫一個配置類是使用屬性(attributes),應用<code>ConfigurationPropertyAttribute</code>,<code>TypeConverterAttribute</code>和各種驗證器屬性(attributes)到簡單類的公有屬性(properties)。這提供了非常快速的開發,但是長遠來說性能是會很差。當.NET 2.0配置架構加載這樣一個配置類,大量的反射和對象構造必須随之建立必要的<code>ConfigurationProperty</code>對象及其相關的驗證器和類型轉換器。當自定義配置類的屬性(properties)集合被通路時,将這樣做,通過下面的代碼片段可以看到:

/// &lt;summary&gt;Gets the collection of properties.&lt;/summary&gt;  

/// &lt;returns&gt;The &lt;see   

/// cref="T:System.Configuration.ConfigurationPropertyCollection"&gt;  

/// &lt;/see&gt; collection of properties for the element.&lt;/returns&gt;  

protected virtual ConfigurationPropertyCollection Properties  

      get 

      {  

            ConfigurationPropertyCollection collection1 = null;  

            if (ConfigurationElement.PropertiesFromType(base.GetType(),   

                                                        out collection1))  

                  ConfigurationElement.ApplyInstanceAttributes(this);  

                  ConfigurationElement.ApplyValidatorsRecursive(this);  

            return collection1;  

      }  

這是預設的<code>ConfigurationElement.Properties</code>屬性。任何時候他被通路,<code>static PropertiesFromType(Type, out ConfigurationPropertyCollection)</code>将被調用,它啟動一個過程,發現并建立在你的自定義配置類中包裝的指定類的任何ConfigurationProperty對象,型。注意,為指定類型建立所有的ConfigurationProperty對象,不隻是你要求的那個。一旦建立一個指定配置的屬性(property)集合,它将被緩存,改進随後調用的性能。

private static bool PropertiesFromType(Type type,   

        out ConfigurationPropertyCollection result)  

      ConfigurationPropertyCollection collection1 =   

       (ConfigurationPropertyCollection)   

        ConfigurationElement.s_propertyBags[type];  

      result = null;  

      bool flag1 = false;  

      if (collection1 == null)  

            lock (ConfigurationElement.s_propertyBags.SyncRoot)  

                  collection1 = (ConfigurationPropertyCollection)   

                                 ConfigurationElement.s_propertyBags[type];  

                  if (collection1 == null)  

                  {  

                        collection1 =   

                         ConfigurationElement.CreatePropertyBagFromType(type);  

                        ConfigurationElement.s_propertyBags[type] =   

                                             collection1;  

                        flag1 = true;  

                  }  

      result = collection1;  

      return flag1;  

同樣值得注意的是,<code>PropertiesFromType</code>函數是靜态的,按照微軟的準則,所有的靜态方法必須是類型安全的。在一個配置類上對任何屬性(property)的多個要求都将通過一個鎖被序列化,挂起所有在競争的線程直到所有給定類型已經建立和緩存ConfigurationProperty對象。如果ConfigurationPropertyCollection沒有被建立,ConfigurationProperty對象被發現并建立通過調用<code>static CreatePropertyBagFromType(Type)</code>。此函數将啟動一個相當漫長的過程,招緻多次反射請求和屬性表查找建立每個ConfigurationProperty對象。

一旦你的ConfigurationProperty對象以建立,并放置在相應的ConfigurationPropertyCollection,為了随後的調用他們将被緩存。但是,如果根本的.config檔案改變了,屬性(properties)必須重新加載,導緻再次建立的代價。他可能完全繞過過程,通過顯示地定義和建立你的ConfigurationProperty對象,用靜态構造器方法。了解為什麼這能提高性能的技巧是了解什麼重寫Properties property做了什麼。比較下面的代碼和原始屬性的代碼:

public override ConfigurationPropertyCollection Properties  

    get 

        return s_properties;  

在重寫的屬性(property),我們完全繞過了基本的代碼,傳回我們準備的屬性(properties)集合。我們的屬性(properties)集合沒有導緻任何反射代價原因在于我們顯示地(明确地)定義每個ConfigurationProperty,以及相應的驗證器和類型轉換器。努力地顯示地執行建立一個自定義配置類與指令地執行的差異是如此的小,沒有理由不這樣做。顯示地編寫配置類也有利于配置重新載入,而配置可用于應用程式更快。(注意:在重新載入配置時,有可能引發随後的第一個加載,而仍在進行中。當這種情況發生時,其他依賴這個的代碼會崩潰或行為古怪。縮短重新裝載的時間有助于減少這種影響,并減少編寫特殊情況下的反應。)

最大限度地用.NET 2.0配置架構一般是指當編寫任何代碼或為任何其他目的使用XML時遵循同樣的最佳做法。最佳做法就是最佳做法,是以不要吝啬了隻因為它的配置,你所處理的。一些最佳實踐,當我為編我的.NET 應用程式寫配置是使用的如下:

切勿使用超過你的需要。如果你的要求非常簡單,使用 <code>&lt;appSettings&gt;</code> 節。如果你需要更多的類型安全,但并不需要完整的自定義配置節,請嘗試使用VS2005的項目設定功能。

不要試圖保持它太“簡單”。一個項目開始時可以用簡單的要求,但随着時間的推移,需求複雜性通常會增加。管理和維護數百或千的 <code>&lt;appSettings&gt;</code> 總是一個乏味的任務。合并成結構化配置節将允許你封裝、子產品化,并簡化你的配置。将一些自定義配置節放在一起花的時間将很快賺回來,減少了配置維護的代價。

代碼重用。盡量保留在你的腦海裡,“重用,重用,重用”。許多應用程式經常需要同一段配置。在設計和實施你的配置節,試圖使他們盡可能地通用,并盡可能最大限度在未來重用。不要編寫一個自定義配置集合,如果一個預制的可以重用。有個,在本系列的第一篇文中列出。

總是寫高性能的配置代碼。很少有簡單的模式,可以應用于自定義配置節以最大化提高性能,如3、關注性能中所描述的。從這些模式的性能提升顯著,而增加的複雜性是很微不足道的。千萬不要說“我以後将修複性能”,到那時已經晚了。;)

這篇譯文已經翻譯完了,後續将翻譯——破解.NET 2.0配置之謎。如果您覺得好,推薦下能讓更多的人看到。

聲明:此文是譯文,實因水準有限,若有翻譯不當之處請不吝指出,以免此譯文誤導他人,在此謝過!

     本文轉自Saylor87 51CTO部落格,原文連結:http://blog.51cto.com/skynet/365566,如需轉載請自行聯系原作者

繼續閱讀