• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
    • 軟件測試技術
    • 軟件測試博客
    • 軟件測試視頻
    • 開源軟件測試技術
    • 軟件測試論壇
    • 軟件測試沙龍
    • 軟件測試資料下載
    • 軟件測試雜志
    • 軟件測試人才招聘
      暫時沒有公告

    字號: | 推薦給好友 上一篇 | 下一篇

    Attribute在.net編程中的應用

    發布: 2007-6-11 13:31 | 作者: 網絡轉載 | 來源: 網絡 | 查看: 148次 | 進入軟件測試論壇討論

    領測軟件測試網
    經常有朋友問,Attribute是什么?它有什么用?好像沒有這個東東程序也能運行。實際上在.Net中,Attribute是一個非常重要的組成部分,為了幫助大家理解和掌握Attribute,以及它的使用方法,特地收集了幾個Attribute使用的例子,提供給大家參考。

    在具體的演示之前,我想先大致介紹一下Attribute。我們知道在類的成員中有property成員,二者在中文中都做屬性解釋,那么它們到底是不是同一個東西呢?從代碼上看,明顯不同,首先就是它們的在代碼中的位置不同,其次就是寫法不同(Attribute必須寫在一對方括符中)。

    什么是Atrribute

    首先,我們肯定Attribute是一個類,下面是msdn文檔對它的描述:
    公共語言運行時允許你添加類似關鍵字的描述聲明,叫做attributes, 它對程序中的元素進行標注,如類型、字段、方法和屬性等。Attributes和Microsoft .NET Framework文件的元數據保存在一起,可以用來向運行時描述你的代碼,或者在程序運行的時候影響應用程序的行為。

    在.NET中,Attribute被用來處理多種問題,比如序列化、程序的安全特征、防止即時編譯器對程序代碼進行優化從而代碼容易調試等等。下面,我們先來看幾個在.NET中標準的屬性的使用,稍后我們再回過頭來討論Attribute這個類本身。(文中的代碼使用C#編寫,但同樣適用所有基于.NET的所有語言)

    Attribute作為編譯器的指令

    在C#中存在著一定數量的編譯器指令,如:#define DEBUG, #undefine DEBUG, #if等。這些指令專屬于C#,而且在數量上是固定的。而Attribute用作編譯器指令則不受數量限制。比如下面的三個Attribute:

    • Conditional:起條件編譯的作用,只有滿足條件,才允許編譯器對它的代碼進行編譯。一般在程序調試的時候使用。
    • DllImport:用來標記非.NET的函數,表明該方法在一個外部的DLL中定義。
    • Obsolete:這個屬性用來標記當前的方法已經被廢棄,不再使用了。

    下面的代碼演示了上述三個屬性的使用:

     
    #define DEBUG //這里定義條件
        
    using System;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
        
    namespace AttributeDemo
    {
       class MainProgramClass
       {
     
          [DllImport("User32.dll")]
          public static extern int MessageBox(int hParent, string Message, string Caption, int Type);
         
          static void Main(string[] args)
          {
             DisplayRunningMessage();
             DisplayDebugMessage();
         
             MessageBox(0,"Hello","Message",0);
         
             Console.ReadLine();
          }
         
          [Conditional("DEBUG")]
          private static void DisplayRunningMessage()
          {
             Console.WriteLine("開始運行Main子程序。當前時間是"+DateTime.Now);
          }
      
          [Conditional("DEBUG")]
          [Obsolete]
          private static void DisplayDebugMessage()
          {
             Console.WriteLine("開始Main子程序");
          }
       }
    }  
    

    如果在一個程序元素前面聲明一個Attribute,那么就表示這個Attribute被施加到該元素上,前面的代碼,[DllImport]施加到MessageBox函數上, [Conditional]施加到DisplayRuntimeMessage方法和DisplayDebugMessage方法,[Obsolete]施加到DisplayDebugMessage方法上。

    根據上面涉及到的三個Attribute的說明,我們可以猜到程序運行的時候產生的輸出:DllImport Attribute表明了MessageBox是User32.DLL中的函數,這樣我們就可以像內部方法一樣調用這個函數。

    重要的一點就是Attribute就是一個類,所以DllImport也是一個類,Attribute類是在編譯的時候被實例化的,而不是像通常的類那樣在運行時候才實例化。Attribute實例化的時候根據該Attribute類的設計可以帶參數,也可以不帶參數,比如DllImport就帶有"User32.dll"的參數。Conditional對滿足參數的定義條件的代碼進行編譯,如果沒有定義DEBUG,那么該方法將不被編譯,讀者可以把#define DEBUG一行注釋掉看看輸出的結果(release版本,在Debug版本中Conditional的debug總是成立的)。Obsolete表明了DispalyDebugMessage方法已經過時了,它有一個更好的方法來代替它,當我們的程序調用一個聲明了Obsolete的方法時,那么編譯器會給出信息,Obsolete還有其他兩個重載的版本。大家可以參考msdn中關于的ObsoleteAttribute 類的描述。

    Attribute類

    除了.NET提供的那些Attribute派生類之外,我們可以自定義我們自己的Attribute,所有自定義的Attribute必須從Attribute類派生,F在我們來看一下Attribute 類的細節:

    protected Attribute(): 保護的構造器,只能被Attribute的派生類調用。

    三個靜態方法:

    static Attribute GetCustomAttribute():這個方法有8種重載的版本,它被用來取出施加在類成員上指定類型的Attribute。

    static Attribute[] GetCustomAttributes(): 這個方法有16種重載版本,用來取出施加在類成員上指定類型的Attribute數組。

    static bool IsDefined():由八種重載版本,看是否指定類型的定制attribute被施加到類的成員上面。

    實例方法:

    bool IsDefaultAttribute(): 如果Attribute的值是默認的值,那么返回true。

    bool Match():表明這個Attribute實例是否等于一個指定的對象。

    公共屬性: TypeId: 得到一個唯一的標識,這個標識被用來區分同一個Attribute的不同實例。

    我們簡單地介紹了Attribute類的方法和屬性,還有一些是從object繼承來的。這里就不列出來了。

    下面介紹如何自定義一個Attribute: 自定義一個Attribute并不需要特別的知識,其實就和編寫一個類差不多。自定義的Attribute必須直接或者間接地從Attribute這個類派生,如:

    public MyCustomAttribute : Attribute { ... }

    這里需要指出的是Attribute的命名規范,也就是你的Attribute的類名+"Attribute",當你的Attribute施加到一個程序的元素上的時候,編譯器先查找你的Attribute的定義,如果沒有找到,那么它就會查找“Attribute名稱"+Attribute的定義。如果都沒有找到,那么編譯器就報錯。

    對于一個自定義的Attribute,你可以通過AttributeUsage的Attribute來限定你的Attribute 所施加的元素的類型。代碼形式如下: [AttriubteUsage(參數設置)] public 自定義Attribute : Attribute { ... }

    非常有意思的是,AttributeUsage本身也是一個Attribute,這是專門施加在Attribute類的Attribute. AttributeUsage自然也是從Attribute派生,它有一個帶參數的構造器,這個參數是AttributeTargets的枚舉類型。下面是AttributeTargets 的定義:

    public enum AttributeTargets
    {
       All=16383,
       Assembly=1,
       Module=2,
       Class=4,
       Struct=8,
       Enum=16,
       Constructor=32,
       Method=64,
       Property=128,
       Field=256,
       Event=512,
       Interface=1024,
       Parameter=2048,
       Delegate=4096,
       ReturnValue=8192
    }     
             
         

    作為參數的AttributeTarges的值允許通過“或”操作來進行多個值得組合,如果你沒有指定參數,那么默認參數就是All 。 AttributeUsage除了繼承Attribute 的方法和屬性之外,還定義了以下三個屬性:

    AllowMultiple: 讀取或者設置這個屬性,表示是否可以對一個程序元素施加多個Attribute 。

    Inherited:讀取或者設置這個屬性,表示是否施加的Attribute 可以被派生類繼承或者重載。

    ValidOn: 讀取或者設置這個屬性,指明Attribute 可以被施加的元素的類型。

    AttributeUsage 的使用例子:

    using System; 
    namespace AttTargsCS 
    { 
    
       // 該Attribute只對類有效. 
       [AttributeUsage(AttributeTargets.Class)]
       public class ClassTargetAttribute : Attribute 
       { 
        } 
    
    
       // 該Attribute只對方法有效. 
       [AttributeUsage(AttributeTargets.Method)]
       public class MethodTargetAttribute : Attribute 
       { 
        } 
    
    
       // 該Attribute只對構造器有效。
       [AttributeUsage(AttributeTargets.Constructor)]
       public class ConstructorTargetAttribute : Attribute 
       { 
        } 
    
    
       // 該Attribute只對字段有效. 
       [AttributeUsage(AttributeTargets.Field)]
       public class FieldTargetAttribute : Attribute
       {
       } 
    
       
      // 該Attribute對類或者方法有效(組合). 
      [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
       public class ClassMethodTargetAttribute : Attribute
       {
        } 
    
    
       // 該Attribute對所有的元素有效.
       [AttributeUsage(AttributeTargets.All)]
       public class AllTargetsAttribute : Attribute 
      { 
       } 
    
       //上面定義的Attribute施加到程序元素上的用法
       [ClassTarget]  //施加到類
       [ClassMethodTarget]//施加到類
       [AllTargets] //施加到類
       public class TestClassAttribute
       { 
          [ConstructorTarget] //施加到構造器
          [AllTargets] //施加到構造器
          TestClassAttribute()
          { 
           } 
    
          [MethodTarget] //施加到方法
          [ClassMethodTarget] //施加到方法
          [AllTargets] //施加到方法
          public void Method1()
          {
          }
         
          [FieldTarget] //施加到字段
          [AllTargets] //施加到字段
          public int myInt; 
    
          static void Main(string[] args)
          { 
          } 
       }
    } 
         
         
    

     

    至此,我們介紹了有關Attribute類和它們的代碼格式。你一定想知道到底如何在你的應用程序中使用Attribute,如果僅僅是前面介紹的內容,還是不足以說明Attribute有什么實用價值的話,那么從后面的章節開始我們將介紹幾個Attribute的不同用法,相信你一定會對Attribute有一個新的了解。

    .NET Framework中對Attribute的支持是一個全新的功能,這種支持來自它的Attribute類。在你的程序中適當地使用這個類,或者是靈活巧妙地利用這個類,將使你的程序獲得某種在以往編程中很難做到的能力。我們來看一個例子:
    假如你是一個項目開發小組中的成員,你想要跟蹤項目代碼檢查的信息,通常你可以把代碼的檢查信息保存在數據庫中以便查詢;或者把信息寫到代碼的注釋里面,這樣可以閱讀代碼的同時看到代碼被檢查的信息。我們知道.NET的組件是自描述的,那么是否可以讓代碼自己來描述它被檢查的信息呢?這樣我們既可以將信息和代碼保存在一起,又可以通過代碼的自我描述得到信息。答案就是使用Attribute.
    下面的步驟和代碼告訴你怎么做:
    首先,我們創建一個自定義的Attribute,并且事先設定我們的Attribute將施加在class的元素上面以獲取一個類代碼的檢查信息。


    using System;
    using System.Reflection;

    [AttributeUsage(AttributeTargets.Class)] //還記得上一節的內容嗎?
    public class CodeReviewAttribute : System.Attribute //定義一個CodeReview的Attribute
    {
    private string reviewer; //代碼檢查人
    private string date; //檢查日期
    private string comment; //檢查結果信息

    //參數構造器
    public CodeReviewAttribute(string reviewer, string date)
    {
    this.reviewer=reviewer;
    this.date=date;
    }

    public string Reviewer
    {
    get
    {
    return reviewer;
    }
    }

    public string Date
    {
    get
    {
    return date;
    }
    }

    public string Comment
    {
    get
    {
    return comment;
    }
    set
    {
    comment=value;
    }
    }
    }

    用于參數的Attribute

    在編寫多層應用程序的時候,你是否為每次要寫大量類似的數據訪問代碼而感到枯燥無味?比如我們需要編寫調用存儲過程的代碼,或者編寫T_SQL代碼,這些代碼往往需要傳遞各種參數,有的參數個數比較多,一不小心還容易寫錯。有沒有一種一勞永逸的方法?當然,你可以使用MS的Data Access Application Block,也可以使用自己編寫的Block。這里向你提供一種另類方法,那就是使用Attribute。

    下面的代碼是一個調用AddCustomer存儲過程的常規方法:

    public int AddCustomer(SqlConnection connection, 
    		string customerName, 
    		string country, 
    		string province, 
    		string city, 
    		string address, 
    		string telephone)
    {
       SqlCommand command=new SqlCommand("AddCustomer", connection);
       command.CommandType=CommandType.StoredProcedure;
    
       command.Parameters.Add("@CustomerName",SqlDbType.NVarChar,50).Value=customerName;
       command.Parameters.Add("@country",SqlDbType.NVarChar,20).Value=country;
       command.Parameters.Add("@Province",SqlDbType.NVarChar,20).Value=province;
       command.Parameters.Add("@City",SqlDbType.NVarChar,20).Value=city;
       command.Parameters.Add("@Address",SqlDbType.NVarChar,60).Value=address;
       command.Parameters.Add("@Telephone",SqlDbType.NvarChar,16).Value=telephone;
       command.Parameters.Add("@CustomerId",SqlDbType.Int,4).Direction=ParameterDirection.Output;
    
       connection.Open();
       command.ExecuteNonQuery();
       connection.Close();
    
       int custId=(int)command.Parameters["@CustomerId"].Value;
       return custId;
    }		
    		

    上面的代碼,創建一個Command實例,然后添加存儲過程的參數,然后調用ExecuteMonQuery方法執行數據的插入操作,最后返回CustomerId。從代碼可以看到參數的添加是一種重復單調的工作。如果一個項目有100多個甚至幾百個存儲過程,作為開發人員的你會不會要想辦法偷懶?(反正我會的:-))。

    下面開始我們的代碼自動生成工程:

    我們的目的是根據方法的參數以及方法的名稱,自動生成一個Command對象實例,第一步我們要做的就是創建一個SqlParameterAttribute, 代碼如下:

    SqlCommandParameterAttribute.cs
    
    using System;
    using System.Data;
    using Debug=System.Diagnostics.Debug;
    
    namespace DataAccess
    {
       // SqlParemeterAttribute 施加到存儲過程參數
       [ AttributeUsage(AttributeTargets.Parameter) ]
       public class SqlParameterAttribute : Attribute
       {
          private string name;      				//參數名稱
          private bool paramTypeDefined;     //是否參數的類型已經定義
          private SqlDbType paramType;       //參數類型
          private int size;                  //參數尺寸大小
          private byte precision;            //參數精度
          private byte scale;                //參數范圍
          private bool directionDefined;     //是否定義了參數方向
          private ParameterDirection direction;  //參數方向
          
          public SqlParameterAttribute()
          {
          }
          
          public string Name
          {
             get { return name == null ? string.Empty : name; }
             set { _name = value; }
          }
          
          public int Size
          {
             get { return size; }
             set { size = value; }
          }
          
          public byte Precision
          {
             get { return precision; }
             set { precision = value; }
          }
          
          public byte Scale
          {
             get { return scale; }
             set { scale = value; }
          }
          
          public ParameterDirection Direction
          {
             get
             {
                Debug.Assert(directionDefined);
                return direction;
             }
             set
             {
                direction = value; 
    		    directionDefined = true;
    		 }
          }
          
          public SqlDbType SqlDbType
          {
             get
             {
                Debug.Assert(paramTypeDefined);
                return paramType;
             }
             set
             {
                paramType = value;
                paramTypeDefined = true;
             }
          }
          
          public bool IsNameDefined
          {
             get { return name != null && name.Length != 0; }
          }
          
          public bool IsSizeDefined
          {
             get { return size != 0; }
          }
          
          public bool IsTypeDefined
          {
             get { return paramTypeDefined; }
          }
          
          public bool IsDirectionDefined
          {
             get { return directionDefined; }
          }
          
          public bool IsScaleDefined
          {
             get { return _scale != 0; }
          }
          
          public bool IsPrecisionDefined
          {
             get { return _precision != 0; }
          }
          
          ...
          
    以上定義了SqlParameterAttribute的字段和相應的屬性,為了方便Attribute的使用,我們重載幾個構造器,不同的重載構造器用于不用的參數:
          ...
    
          // 重載構造器,如果方法中對應于存儲過程參數名稱不同的話,我們用它來設置存儲過程的名稱
          // 其他構造器的目的類似
          public SqlParameterAttribute(string name)
          {
             Name=name;
          }
    
          public SqlParameterAttribute(int size)
          {
             Size=size;
          }
          
          public SqlParameterAttribute(SqlDbType paramType)
          {
             SqlDbType=paramType;
          }
          
          public SqlParameterAttribute(string name, SqlDbType paramType)
          {
             Name = name;
             SqlDbType = paramType;
          }
          
          public SqlParameterAttribute(SqlDbType paramType, int size)
          {
             SqlDbType = paramType;
             Size = size;
          }
          
          
          public SqlParameterAttribute(string name, int size)
          {
             Name = name;
             Size = size;
          }
          
          public SqlParameterAttribute(string name, SqlDbType paramType, int size)
          {
             Name = name;
             SqlDbType = paramType;
             Size = size;
          }
       }
    }
    

    為了區分方法中不是存儲過程參數的那些參數,比如SqlConnection,我們也需要定義一個非存儲過程參數的Attribute:

    //NonCommandParameterAttribute.cs
    
    using System;
    namespace DataAccess
    {
       [ AttributeUsage(AttributeTargets.Parameter) ]
       public sealed class NonCommandParameterAttribute : Attribute
       {
       }
    }
    

    我們已經完成了SQL的參數Attribute的定義,在創建Command對象生成器之前,讓我們考慮這樣的一個事實,那就是如果我們數據訪問層調用的不是存儲過程,也就是說Command的CommandType不是存儲過程,而是帶有參數的SQL語句,我們想讓我們的方法一樣可以適合這種情況,同樣我們仍然可以使用Attribute,定義一個用于方法的Attribute來表明該方法中的生成的Command的CommandType是存儲過程還是SQL文本,下面是新定義的Attribute的代碼:

    //SqlCommandMethodAttribute.cs
    
    using System;
    using System.Data;
    
    namespace Emisonline.DataAccess
    {
       [AttributeUsage(AttributeTargets.Method)]
       public sealed class SqlCommandMethodAttribute : Attribute
       {
          private string commandText;
          private CommandType commandType;
    
          public SqlCommandMethodAttribute( CommandType commandType, string commandText)
          {
             commandType=commandType;
             commandText=commandText;
          }
    
          public SqlCommandMethodAttribute(CommandType commandType) : this(commandType, null){}
    
          public string CommandText
          {
             get
             {
                return commandText==null ? string.Empty : commandText;
             }
             set
             {
                commandText=value;
             }
          }
    
          public CommandType CommandType
          {
             get
             {
                 return commandType;
             }
             set
             {
                commandType=value;
             }
          }
       }
    }
    		
    

    我們的Attribute的定義工作已經全部完成,下一步就是要創建一個用來生成Command對象的類。

    SqlCommandGenerator類的設計

    SqlCommandGEnerator類的設計思路就是通過反射得到方法的參數,使用被SqlCommandParameterAttribute標記的參數來裝配一個Command實例。

    引用的命名空間:

    //SqlCommandGenerator.cs
    
    using System;
    using System.Reflection;
    using System.Data;
    using System.Data.SqlClient;
    using Debug = System.Diagnostics.Debug;
    using StackTrace = System.Diagnostics.StackTrace;  
    
    

    類代碼:

    namespace DataAccess
    {
       public sealed class SqlCommandGenerator
       {
          //私有構造器,不允許使用無參數的構造器構造一個實例
          private SqlCommandGenerator()
          {
             throw new NotSupportedException();
          }
    
          //靜態只讀字段,定義用于返回值的參數名稱
          public static readonly string ReturnValueParameterName = "RETURN_VALUE";
          //靜態只讀字段,用于不帶參數的存儲過程
          public static readonly object[] NoValues = new object[] {};
       
          
          public static SqlCommand GenerateCommand(SqlConnection connection,
                                      MethodInfo method, object[] values)
          {
             //如果沒有指定方法名稱,從堆棧幀得到方法名稱
             if (method == null)
                 method = (MethodInfo) (new StackTrace().GetFrame(1).GetMethod());
    
             // 獲取方法傳進來的SqlCommandMethodAttribute
             // 為了使用該方法來生成一個Command對象,要求有這個Attribute。
             SqlCommandMethodAttribute commandAttribute = 
                (SqlCommandMethodAttribute) Attribute.GetCustomAttribute(method, typeof(SqlCommandMethodAttribute));
    
             Debug.Assert(commandAttribute != null);
             Debug.Assert(commandAttribute.CommandType == CommandType.StoredProcedure ||
           commandAttribute.CommandType == CommandType.Text);
    
             // 創建一個SqlCommand對象,同時通過指定的attribute對它進行配置。
             SqlCommand command = new SqlCommand();
             command.Connection = connection;
             command.CommandType = commandAttribute.CommandType;
          
             // 獲取command的文本,如果沒有指定,那么使用方法的名稱作為存儲過程名稱 
             if (commandAttribute.CommandText.Length == 0)
             {
                Debug.Assert(commandAttribute.CommandType == CommandType.StoredProcedure);
                command.CommandText = method.Name;
             }
             else
             {
                command.CommandText = commandAttribute.CommandText;
             }
    
             // 調用GeneratorCommandParameters方法,生成command參數,同時添加一個返回值參數
             GenerateCommandParameters(command, method, values);
             command.Parameters.Add(ReturnValueParameterName, SqlDbType.Int).Direction 
                                  =ParameterDirection.ReturnValue;
    
             return command;
          }
    
          private static void GenerateCommandParameters(
                               SqlCommand command, MethodInfo method, object[] values)
          {
    
             // 得到所有的參數,通過循環一一進行處理。
             
             ParameterInfo[] methodParameters = method.GetParameters();
             int paramIndex = 0;
    
             foreach (ParameterInfo paramInfo in methodParameters)
             {
                // 忽略掉參數被標記為[NonCommandParameter ]的參數
             
                if (Attribute.IsDefined(paramInfo, typeof(NonCommandParameterAttribute)))
                   continue;
                
                // 獲取參數的SqlParameter attribute,如果沒有指定,那么就創建一個并使用它的缺省設置。
                SqlParameterAttribute paramAttribute = (SqlParameterAttribute) Attribute.GetCustomAttribute(
         paramInfo, typeof(SqlParameterAttribute));
       
          if (paramAttribute == null)
             paramAttribute = new SqlParameterAttribute();
          
          //使用attribute的設置來配置一個參數對象。使用那些已經定義的參數值。如果沒有定義,那么就從方法 
          // 的參數來推斷它的參數值。
          SqlParameter sqlParameter = new SqlParameter();
          if (paramAttribute.IsNameDefined)
             sqlParameter.ParameterName = paramAttribute.Name;
          else
             sqlParameter.ParameterName = paramInfo.Name;
    
                if (!sqlParameter.ParameterName.StartsWith("@"))
                   sqlParameter.ParameterName = "@" + sqlParameter.ParameterName;
             
                if (paramAttribute.IsTypeDefined)
                   sqlParameter.SqlDbType = paramAttribute.SqlDbType;
                
                if (paramAttribute.IsSizeDefined)
                   sqlParameter.Size = paramAttribute.Size;
    
                if (paramAttribute.IsScaleDefined)
                   sqlParameter.Scale = paramAttribute.Scale;
                
                if (paramAttribute.IsPrecisionDefined)
                   sqlParameter.Precision = paramAttribute.Precision;
                
                if (paramAttribute.IsDirectionDefined)
                {
                   sqlParameter.Direction = paramAttribute.Direction;
                }
                else
                {
                   if (paramInfo.ParameterType.IsByRef)
                   {
                      sqlParameter.Direction = paramInfo.IsOut ? 
                                  ParameterDirection.Output : 
                                  ParameterDirection.InputOutput;
                   }
                   else
                   {
                      sqlParameter.Direction = ParameterDirection.Input;
                   }
                }
             
                // 檢測是否提供的足夠的參數對象值
         Debug.Assert(paramIndex < values.Length);
               
               //把相應的對象值賦于參數。
               sqlParameter.Value = values[paramIndex];
               command.Parameters.Add(sqlParameter);
                      
                      
               paramIndex++;
             }
          
             //檢測是否有多余的參數對象值
             Debug.Assert(paramIndex == values.Length);
          }
       }
    }
    

    必要的工作終于完成了。SqlCommandGenerator中的代碼都加上了注釋,所以并不難讀懂。下面我們進入最后的一步,那就是使用新的方法來實現上一節我們一開始顯示個那個AddCustomer的方法。

    重構新的AddCustomer代碼:

    [ SqlCommandMethod(CommandType.StoredProcedure) ]
    public void AddCustomer( [NonCommandParameter] SqlConnection connection, 
                       [SqlParameter(50)] string customerName, 
                       [SqlParameter(20)] string country, 
                       [SqlParameter(20)] string province, 
                       [SqlParameter(20)] string city, 
                       [SqlParameter(60)] string address, 
                       [SqlParameter(16)] string telephone,
                       out int customerId )
    {
       customerId=0; //需要初始化輸出參數
      //調用Command生成器生成SqlCommand實例
       SqlCommand command = SqlCommandGenerator.GenerateCommand( connection, null, new object[]
    {customerName,country,province,city,address,telephone,customerId } );
                             
       connection.Open();
       command.ExecuteNonQuery();
       connection.Close();
    
       //必須明確返回輸出參數的值
       customerId=(int)command.Parameters["@CustomerId"].Value;
    }
    

    代碼中必須注意的就是out參數,需要事先進行初始化,并在Command執行操作以后,把參數值傳回給它。受益于Attribute,使我們擺脫了那種編寫大量枯燥代碼編程生涯。 我們甚至還可以使用Sql存儲過程來編寫生成整個方法的代碼,如果那樣做的話,可就大大節省了你的時間了,上一節和這一節中所示的代碼,你可以把它們單獨編譯成一個組件,這樣就可以在你的項目中不斷的重用它們了。從下一節開始,我們將更深層次的介紹Attribute的應用,請繼續關注。

    Attribute在攔截機制上的應用

    從這一節開始我們討論Attribute的高級應用,為此我準備了一個實際的例子:我們有一個訂單處理系統,當一份訂單提交的時候,系統檢查庫存,如果庫存存量滿足訂單的數量,系統記錄訂單處理記錄,然后更新庫存,如果庫存存量低于訂單的數量,系統做相應的記錄,同時向庫存管理員發送郵件。為了方便演示,我們對例子進行了簡化:

    //Inventory.cs
    using System;
    using System.Collections;
    
    namespace NiwalkerDemo
    {
       public class Inventory
       {
    
          private Hashtable inventory=new Hashtable();
          
          public Inventory()
          {
             inventory["Item1"]=100;
             inventory["Item2"]=200;
          }
    
          public bool Checkout(string product, int quantity)
          {
             int qty=GetQuantity(product);
           	   return qty>=quantity;
          }
          
          public int GetQuantity(string product)
          {
             int qty=0;
             if(inventory[product]!=null)
                qty = (int)inventory[product];
             return qty;
          }
          
          public void Update(string product, int quantity)
          {
             int qty=GetQuantity(product);
             inventory[product]=qty-quantity;
          }
       }
    }
    
    //Logbook.cs
    using System;
    
    namespace NiwalkerDemo
    {
       public class Logbook
       {
          public static void Log(string logData)
          {
             Console.WriteLine("log:{0}",logData);
          }
       }
    }
    
    //Order.cs
    using System;
    
    namespace NiwalkerDemo
    {
       public class Order
       {
          private int orderId;
          private string product;
          private int quantity;
          
          public Order(int orderId)
          {
             this.orderId=orderId;
          }
          
          public void Submit()
          {
             Inventory inventory=new Inventory(); //創建庫存對象
             
             //檢查庫存
             if(inventory.Checkout(product,quantity))
             {
                Logbook.Log("Order"+orderId+" available");
                inventory.Update(product,quantity);
             }
             else
             {
                Logbook.Log("Order"+orderId+" unavailable");
                SendEmail();
             }
          }
          
          public string ProductName
          {
             get{ return product; }
             set{ product=value;  }
          }
          
          public int OrderId
          {
             get{ return orderId; }
          }
          
          public int Quantity
          {
             get{ return quantity;}
             set{ quantity=value; }
          }
          
          public void SendEmail()
          {
             Console.WriteLine("Send email to manager");
          }
       }
    }
    
    


    下面是調用程序:

    //AppMain.cs
    
    using System;
    
    namespace NiwalkerDemo
    {
       public class AppMain
       {
          static void Main()
          {
             Order order1=new Order(100);
             order1.ProductName="Item1";
             order1.Quantity=150;
             order1.Submit();
             
             Order order2=new Order(101);
             order2.ProductName="Item2";
             order2.Quantity=150;
             order2.Submit();
          }
       }
    }
    
    

    程序看上去還不錯,商務對象封裝了商務規則,運行的結果也符合要求。但是我好像聽到你在抱怨了,沒有嗎?當你的客戶的需求改變的時候(客戶總是經常改變他們的需求),比如庫存檢查的規則不是單一的檢查產品的數量,還要檢查產品是否被預訂的多種情況,那么你需要改變Inventory的代碼,同時還要修改Order中的代碼,我們的例子只是一個簡單的商務邏輯,實際的情況比這個要復雜的多。問題在于Order對象同其他的對象之間是緊耦合的,從OOP的觀點出發,這樣的設計是有問題的,如果你寫出這樣的程序,至少不會在我的團隊里面被Pass.

    你說了:“No problem! 我們可以把商務邏輯抽出來放到一個專門設計的用來處理事務的對象中!编,好主意,如果你是這么想的,或許我還可以給你一個提議,使用Observer Design Pattern(觀察者設計模式):你可以使用delegate,在Order對象中定義一個BeforeSubmit和AfterSubmit事件,然后創建一個對象鏈表,將相關的對象插入到這個鏈表中,這樣就可以實現對Order提交事件的攔截,在Order提交之前和提交之后自動進行必要的事務處理。如果你感興趣的話,你可以自己動手來編寫這樣的一個代碼,或許還要考慮在分布式環境中(Order和Inventory不在一個地方)如何處理對象之間的交互問題。

    幸運的是,.NET Framework中提供了實現這種技術的支持。在.NET Framework中的對象Remoting和組件服務中,有一個重要的攔截機制,在對象Remoting中,不同的應用程序之間的對象的交互需要穿越他們的域邊界,每一個應用域也可以細分為多個Context(上下文環境),每一個應用域也至少有一個默認的Context,即使在同一個應用域,也存在穿越不同Context的問題。NET的組件服務發展了COM+的組件服務,它使用Context Attribute來實現象COM+一樣的攔截功能。通過對調用對象的攔截,我們可以對一個方法的調用進行前處理和后處理,同時也解決了上述的跨越邊界的問題。

    需要提醒你,如果你在MSDN文檔查ContextAttribute,我可以保證你得不到任何有助于了解ContextAttribute的資料,你看到的將是這么一句話:“This type supports the .NET Framework infrastructure and is not intended to be used directly from your code.”——“本類型支持.NET Framework基礎結構,它不打算直接用于你的代碼!辈贿^,在msdn站點,你可以看到一些有關這方面的資料(見文章后面的參考鏈接)。

    下面我們介紹有關的幾個類和一些概念,首先是:

    ContextAttribute類

    ContextAttribute派生自Attribute,同時它還實現了IContextAttribute和IContextProperty接口。所有自定義的ContextAttribute必須從這個類派生。
    構造器:
    ContextAttribute:構造器帶有一個參數,用來設置ContextAttribute的名稱。

    公共屬性:
    Name:只讀屬性。返回ContextAttribute的名稱

    公共方法:
    GetPropertiesForNewContext:虛擬方法。向新的Context添加屬性集合。
    IsContextOK:虛擬方法。查詢客戶Context中是否存在指定的屬性。
    IsNewContextOK:虛擬方法。默認返回true。一個對象可能存在多個Context,使用這個方法來檢查新的Context中屬性是否存在沖突。
    Freeze:虛擬方法。該方法用來定位被創建的Context的最后位置。

    ContextBoundObject類

    實現被攔截的類,需要從ContextBoundObject類派生,這個類的對象通過Attribute來指定它所在Context,凡是進入該Context的調用都可以被攔截。該類從MarshalByRefObject派生。

    以下是涉及到的接口:

    IMessage:定義了被傳送的消息的實現。一個消息必須實現這個接口。

    IMessageSink:定義了消息接收器的接口,一個消息接收器必須實現這個接口。

    還有幾個接口,我們將在下一節結合攔截構架的實現原理中進行介紹。


    我們的自定義CodeReviewAttribute同普通的類沒有區別,它從Attribute派生,同時通過AttributeUsage表示我們的Attribute僅可以施加到類元素上。

    第二步就是使用我們的CodeReviewAttribute, 假如我們有一個Jack寫的類MyClass,檢查人Niwalker,檢查日期2003年7月9日,于是我們施加Attribute如下:

    [CodeReview("Niwalker","2003-7-9",Comment="Jack的代碼")]
    public class MyClass
    {
    //類的成員定義


    當這段代碼被編譯的時候,編譯器會調用CodeReviewAttribute的構造器并且把"Niwalker"和"2003-7-9"分別作為構造器的參數。注意到參數表中還有一個Comment屬性的賦值,這是Attribute特有的方式,這里你可以設置更多的Attribute的公共屬性(如果有的話),需要指出的是.NET Framework1.0允許向private的屬性賦值,但在.NET Framework1.1已經不允許這樣做,只能向public的屬性賦值。

    第三步就是取出我們需要的信息,這是通過.NET的反射來實現的,關于反射的知識,限于篇幅我不打算在這里進行說明,也許我會在以后另外寫一篇介紹反射的文章。

    class test
    {
    static void Main(string[] args)
    {
    System.Reflection.MemberInfo info=typeof(MyClass); //通過反射得到MyClass類的信息

    //得到施加在MyClass類上的定制Attribute 
    CodeReviewAttribute att=
    (CodeReviewAttribute)Attribute.GetCustomAttribute(info,typeof(CodeReviewAttribute)); 
    if(att!=null)
    {
    Console.WriteLine("代碼檢查人:{0}",att.Reviewer);
    Console.WriteLine("檢查時間:{0}",att.Date);
    Console.WriteLine("注釋:{0}",att.Comment);
    }
    }
    }

    在上面這個例子中,Attribute扮演著向一個類添加額外信息的角色,它并不影響MyClass類的行為。通過這個例子,我們大致可以知道如何寫一個自定義的Attribute,以及如何在應用程序使用它。下一節,我將介紹如何使用Attribute來自動生成ADO.NET的數據訪問類的代碼。

    延伸閱讀

    文章來源于領測軟件測試網 http://www.kjueaiud.com/

    TAG: net 編程 應用 中的 attribute


    關于領測軟件測試網 | 領測軟件測試網合作伙伴 | 廣告服務 | 投稿指南 | 聯系我們 | 網站地圖 | 友情鏈接
    版權所有(C) 2003-2010 TestAge(領測軟件測試網)|領測國際科技(北京)有限公司|軟件測試工程師培訓網 All Rights Reserved
    北京市海淀區中關村南大街9號北京理工科技大廈1402室 京ICP備10010545號-5
    技術支持和業務聯系:info@testage.com.cn 電話:010-51297073

    軟件測試 | 領測國際ISTQBISTQB官網TMMiTMMi認證國際軟件測試工程師認證領測軟件測試網

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>