[C#]特性與屬性

  • 16362
  • 0
  • 2011-01-26

摘要:[C#]特性與屬性

本篇將介紹以下內容:

●定製特性的基本概念與用法

●特性與屬性的區別比較

反映(反射)的簡單介紹

1.引言

attribute(特性MSDN上稱為屬性)是.Net框架引入的有一技術亮點,因此我們有必要花點時間來了解本篇的內容,走進一個發現attribute登堂入室的入口。因為.Net Framework中使用大量的定制特性來完成程式碼約定,[Serializable]、[Flags]、[DllImport]、[AttributeUsage]這些的構造,相信我們都見過吧,那麼你是否了解其背後的技術。

 提起特性(MSDN上稱為屬性),由於高級語言發展的歷史原因,不免讓人想起另一個耳熟能詳的名字:屬性。特性(屬性)和屬性,往往給出學者或者從C++轉移到C#的人混淆的概念衝擊。那麼,什麼是屬性,什麼是特性,兩者的概念和區別,用法語範例,將在本篇做以概括性的總結和比較,希望給你的理解帶來收獲。另外本篇的主題以特性的介紹為主,屬性論述重點突出在兩者的比較上。

 

 

2.概念引入

2.1.什麼是特性(MSDN上稱為屬性)?

MSDN的定義為:公共語言執行時允許添加類似關鍵字的描述聲明,叫做attributes,它對程式中的項目(元素)進行標註,如資料型別(類型)、欄位(字段)、方法和屬性等。Attributes和Microsoft .NET Framework文件的中繼資料(元數據)保存在一起,可以用來巷執行時描述你的程式碼,或者在程式執行的時候影響應用程式的行為。

我們簡單的總結為:定制特性attribute,本質上是一個類,其為目標項目(元素)提供關聯附加訊息,並在執行期以反射的方式來獲取附加訊息。具體的特性實現方法,在接下來的討論中繼續深入。

2.2.什麼是屬性(Property)?

屬性是物件導向程式的基本概念,提供了對私有欄位(字段)的訪問封裝,在C#中以getset 索引子(訪問器)方法來實現對可讀可寫屬性的操作,提供了安全和靈活的資料訪問封裝。關於屬性的概念,不是本篇的重點,而且相信大部分的技術人員應該對屬性有清晰的概念。

 

以下是簡單的屬性範例:

public class MyProperty
        {
            //定義欄位(字段)
            private string _name;
            private int _age;

            //定義屬性,實現對_name欄位(字段)的封裝
            public string Name
            {
            //加入對欄位(字段)範圍控制
                get { return (_name == null) ? string.Empty : _name; }
                set { _name = value; }
            }

            //定義屬性,實現對_age欄位(字段)的封裝
            //加入對欄位(字段)範圍控制
            public int Age
            {
                get { return _age; }
                set
                {
                    if ((value > 0) && (value < 150))
                    {
                        _age = value;
                    }
                    else
                    {
                        throw new Exception("Not a real age");
                    }
                }
            }
        }

        public class MyTest
        {
            public static void Main(string[] args)
            {
                MyProperty myProperty = new MyProperty();
                myProperty.Name = "Anytao";//觸發set存取子(訪問器)
                Console.WriteLine(myProperty.Name); //觸發get存取子(訪問器)
                myProperty.Age = 66;//觸發set存取子(訪問器)
                Console.WriteLine(myProperty.Age.ToString()); //觸發get存取子(訪問器)
                Console.ReadKey();
            }
        }

2.3.區別與比較

通過對概念的澄清和歷史的回溯,我們知道特性與屬性只是名稱上有過糾葛,在MSDN上關於attribute的中文解釋甚至還是屬性,但是我同意更通常的稱呼:特性。在功能上和應用上,兩者其實沒有太多模糊的概念交叉,因此也沒有必要比較其應用的異同點。本篇則以特性的概念為重點,來討論其應用的場合和規則。

我理解的定制特性,就是為目標項目(元素),可以是資料集(數據集)、模組(模塊)、類、屬性、方法、甚至函數參數等加入附加訊息,類似於註釋,但是可以在執行期以反映(反射)的方式獲得。定制特性主要應用在於序列化、編譯器指令、設計模式等方面。

3.通用規則

●定制特性可以應用的目標項目(元素)可為:組件(程序集、assembly)、模組(模塊、Module)、資料型別(類型
  type)、屬性(property)、事件(event)、欄位(字段、field)、方法(method)、參數(param)、返回值(return)。

●定制特性以[,]形式展現,放在緊接著的項目(元素)上,多個特性可以應用於同一個項目(元素),特性間以逗號隔開,
  以下表達規則有效:[AttributeUsage][ Flags]、[AttributeUsage, Flags]、[Flags, AttibuteUsageAttribute]、
  [AttributeUsage(), FlagesAttribute()]。

●attribute實例,是在編譯期進行出史話,而不是執行期。

●C#允許以指定的前綴來表示特性所應用的目標項目(元素),建議這樣來處理,因為顯示處理可以消除可能帶來的兩異性。例如:

using System; 

namespace Anytao.net 
{
    [assembly: MyAttribute(1)]//應用於組件(程序集)
    [module: MyAttribute(2)]//應用於模組(模塊)
    public class Attribute_how2do
    {
        //
    } 
}

●定制特性類型,必須直接或者間接的繼承自System.Attribute類,而且該類型必須有公有建構函式(構造函數)來創見
  其實例。

●所有自定義的特性名稱都應該有個Attribute後綴,這是習慣性的約定。

●定制特性也可以應用在其他定制特性上,這點也很好理解,因為定制特性本身也是一個類,遵守類的公有規則。例
  如很多時候我們的自定義定制特性會應用AttributeUsageAttribute特性,來控制如何應用新定義的特性。

[AttributeUsageAttribute(AttributeTarget.All),
AllowMultiple = true, 
Inherited = true]
class MyNewAttribute: System.Attribute
{
//
} 

●定制特性不會影響應用項目(元素)的任何功能,只是約定了該項目(元素)具有的特質。 
●所有抽象特性必須具有Public訪問限制。
●特性常用於編譯器指令,突破#define, #undefine, #if, #endif的限制,而且更加靈活。
●定制特性常用於執行期獲得程式碼註釋訊息,以附加訊息來優化調適。
●定制特性可以應用在某些設計模式中,如工廠模式。
●定制特性還常用於標記,非托管函數標記、方法廢棄標記等其他方面。

4.特性的應用

4.1.常用特性

常用特性,也就是.NET已經提供的固有特性,事實上在.NET框架中已經提供了豐富的固有特性由我們發揮,以下經選出我認為最常用、最典型的固有特性做以簡單討論,當然這只是我的一家之言,亦不足道。我想了解特性,還是從這裡作為起點,從.NET提供的經典開始,或許是一種求知的捷徑,希望能給大家啟示。

AttributeUsage

  AttributeUsage特性用於控制如何應用自定義特性到目標項目(元素)。關  
  於uteTargetsAllowMultipleInheritedValidOn,請參閱範例說明和其他文件。我們已
  經做了相當的介紹和範例說明,我們還是在實戰中自己體會更多吧。

Flags

  以Flags特性來將列舉(枚舉)值看作位標記,而非單獨的數值,例如:

enum Animal
{
    Dog     = 0x0001,
    Cat     = 0x0002,
    Duck    = 0x0004,
    Chicken = 0x0008
}

因此,以下實現就相當輕鬆

Animal animals = Animal.Dog | Animal.Cat;
Console.WriteLine(animals.ToString());

猜猜結果是什麼?答案是:"Dog,Cat"。如果沒有Flags特別,這裡的結果將會是"3"。

DllImport

  DllImport特性,可以讓我們調用非托管程式碼,所以我們可以使用DllImport特性引用對Win32 API函數的調用,對
  於習慣了非托管程式碼的程式員來說,這一特性無疑是救命的稻草。

using System;
using System.Runtime.InteropServices;

namespace Anytao.net
{
    class MainClass 
    {
       [DllImport("User32.dll")]
       public static extern int MessageBox(int hParent, string msg, string caption, int type);

       static int Main() 
       {
          return MessageBox(0, "How to use attribute in .NET", "Anytao_net", 0);
       }
    }
}

Serializable  

   Serializable特性表明了應用的項目(元素)可以被序列化(Serializable),序列化和反序列化是另一個可以探入討論的
   話題,在此我們只是提出概念,深入的研究有待以專門的主題來呈現。

Conditional

  Conditional特性,用於條件編譯,在偵錯(調適)時使用。注意:Conditional不可應用於資料成員(數據成員)和屬
  性。

還有其他的重要特性,包括:Description、DefaultValue、Category、ReadOnly、BrowerAble等,有時間可以滲入研究。

4.2.自定義特性

[AttributeUsage(AttributeTargets.Class |
 AttributeTargets.Method,Inherited =  true )]
public  class TestAttribute : System.Attribute
{
    public  TestAttribute( string message)
    {
       Console.WriteLine(message);
    }
    public  void RunTest()
    {
       Console.WriteLine( " TestAttribute here. " );
    }
}

應用目標項目(元素)

[Test( " Error Here. " )]
 public void CannotRun()
 {
    //
 }

獲取項目(元素)附加訊息

  如果沒有什麼機制在執行期來獲取Attribute的附加訊息,那麼attribute就沒有什麼存在的意義。因此,.NET中以反映
  (反射)機制來實現在執行期獲取attribute訊息,實現方法如下:

public static void Main()
  {
    Tester t =  new Tester();
    t.CannotRun();

    Type tp =  typeof (Tester);
    MethodInfo mInfo =  tp.GetMethod( " CannotRun " );            
    TestAttribute myAtt =  (TestAttribute)Attribute.GetCustomAttribute(mInfo, typeof (TestAttribute));
    myAtt.RunTest();
  }

5.經典範例

5.1.小菜一碟

請看註釋

using System;
using System.Reflection;//應用反映(反射)技術獲得特性信息

namespace Anytao.net
{
    // 定制特性也可以應用在其他定制特性上,
    // 應用AttributeUsage,來控制如何應用新定義的特性
    [AttributeUsageAttribute(AttributeTargets.All,// 可應用任何元素
        AllowMultiple = true,// 允許應用多次
        Inherited = false)]// 不繼承到派生類
    // 特性也是一個類,
    // 必須繼承自System.Attribute類,
    // 命名規範為:"類名"+Attribute。        
    public class MyselfAttribute : System.Attribute
    {
        // 定義欄位(字段)
        private string _name;
        private int _age;
        private string _memo;

        // 必須定義其建構函數(構造函數),如果不定義有編譯器提供無參默認建構函式(構造函數)
        public MyselfAttribute()
        {
        }
        public MyselfAttribute(string name, int age)
        {
            _name = name;
            _age = age;
        }

        // 定義屬性
        // 顯然特性和屬性不是一回事兒
        public string Name
        {
            get { return _name == null ? string.Empty : _name; }
        }

        public int Age
        {
            get { return _age; }
        }

        public string Memo
        {
            get { return _memo; }
            set { _memo = value; }
        }

        // 定義方法
        public void ShowName()
        {
            Console.WriteLine(" Hello, {0} ", _name == null ? " world. " : _name);
        }
    }

    // 應用自定義特性
    // 可以以Myself或者MyselfAttribute作為特性名
    // 可以給屬性Memo賦值
    [Myself(" Emma ", 25, Memo = " Emma is my good girl. ")]
    public class Mytest
    {
        public void SayHello()
        {
            Console.WriteLine(" Hello, my.net world. ");
        }
    }

    public class Myrun
    {
        public static void Main(string[] args)
        {
            // 如何以反映(反射)確定特性信息
            Type tp = typeof(Mytest);
            MemberInfo info = tp;
            MyselfAttribute myAttribute =
                (MyselfAttribute)Attribute.GetCustomAttribute(info, typeof(MyselfAttribute));
            if (myAttribute != null)
            {
                // 嘿嘿,在運行時查看註釋內容,是不是很爽
                Console.WriteLine(" Name: {0} ", myAttribute.Name);
                Console.WriteLine(" Age: {0} ", myAttribute.Age);
                Console.WriteLine(" Memo of {0} is {1} ", myAttribute.Name, myAttribute.Memo);
                myAttribute.ShowName();
            }

            // 多點反射
            object obj = Activator.CreateInstance(typeof(Mytest));

            MethodInfo mi = tp.GetMethod(" SayHello ");
            mi.Invoke(obj, null);
            Console.ReadLine();
        }
    }
}


什麼也別想,自己做一下試試。

5.2他山之石

●MSDN認為,特性(Attribute)描述如何將資料(數據)序列化,指定用於強制安全性的特性,並限制實時(JIT)編譯器的
  優化,從而使程式碼易於偵錯(調適)。特性(Attribute)還可以記錄文件名或程式碼作者,或在視窗開發階段控制控制
  項(控件)和成員的可見性。

dudu Boss收藏的系列文章《Attribute在.net編程中的應用》,給你應用方面的啟示會很多,值的研究。

亞歷山大同志的系列文章《手把手教你寫ORM(六)》中也有很好的詮釋。

idioe的文件《Remoting基本原理及其擴展機制》也有收穫。

6.結論

Attribute是.NET引入的一大特色技術,但在網路上討論的不是很多,所以拿出自己的體會來分享,希望就這一技術要點進行一番登堂入室的引導。更深層次的應用,例如序列化、程式安全性、設計模式多方面都可以挖掘出閃耀的金子,這就是.NET在技術領域帶來百變魅力吧。希望大家暢所欲言,來完善和補充作者在這方面的不全面和認知上的不深入,那將是作者最大的鼓勵和動力。

 

©2007 Anytao.com

我只是個小小的入門者