程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 更多關於編程 >> C#中Property和Attribute的區別實例詳解

C#中Property和Attribute的區別實例詳解

編輯:更多關於編程

       本文實例分析了C#中Property和Attribute的區別。分享給大家供大家參考。具體分析如下:

      在C#中有兩個屬性,分別為Property和Attribute,兩個的中文意思都有特性、屬性之間,但是用法上卻不一樣,為了區別,本文暫把Property稱為特性,把Attribute稱為屬性。

      Attribute才是本文的主角,把它稱為屬性我覺得很恰當。屬性的意思就是附屬於某種事物上的,用來說明這個事物的各種特征的一種描述。而Attribute就是干這事的。它允許你將信息與你定義的C#類型相關聯,作為類型的標注。這些信息是任意的,就是說,它不是由語言本身決定的,你可以隨意建立和關聯任何類型的任何信息。你可以作用屬性定義設計時信息和運行時信息,甚至是運行時的行為特征。關鍵在於這些信息不僅可以被用戶取出來作為一種類型的標注,它更可以被編譯器所識別,作為編譯時的一種附屬條件參加程序的編譯。

      以下部分內容及代碼來源於《C#技術揭秘》(Inside C# Sencond Edition)

      定義屬性:

      屬性實際上是一個派生自System.Attribute基類的類。System.Attribute類含有幾個用於訪問和檢查自定義屬性的方法。盡管你有權將任何類定義為屬性,但是按照慣例來說,從System.Attribute派生類是有意義的。示例如下:

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public enum RegHives { HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_CONFIG } public class RegKeyAttribute : Attribute { public RegKeyAttribute(RegHives Hive, String ValueName) { this.Hive = Hive; this.ValueName = ValueName; } protected RegHives hive; public RegHives Hive { get { return hive; } set { hive = value; } } protected String valueName; public String ValueName { get { return valueName; } set { valueName = value; } } }

      我們在這裡添加了不同注冊表的枚舉、屬性類的構造器以及兩個特性(Property)。在定義屬性時你可以做許許多多的事情,下面我們看看如何在運行時查詢屬性。要想在運行時查詢類型或成員所附著的屬性,必須使用反射

      查詢類屬性:

      假設你希望定義一個屬性,這個屬性定義了將在其上創建對象的遠程服務器。如果沒有這個屬性,就要把此信息保存在一個常量中或是一個應用程序的資源文件中。通過使用屬性,只需用以下方法標注出類的遠程服務器名即可:

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 using System; namespace QueryAttribs { public enum RemoteServers { JEANVALJEAN, JAVERT, COSETTE } public class RemoteObjectAttribute : Attribute { public RemoteObjectAttribute(RemoteServers Server) { this.server = Server; } protected RemoteServers server; public string Server { get { return RemoteServers.GetName( typeof(RemoteServers), this.server); } } } [RemoteObject(RemoteServers.COSETTE)] class MyRemotableClass { } class Test { [STAThread] static void Main(string[] args) { Type type = typeof(MyRemotableClass); foreach (Attribute attr in type.GetCustomAttributes(true)) { RemoteObjectAttribute remoteAttr = attr as RemoteObjectAttribute; if (null != remoteAttr) { Console.WriteLine( "Create this object on {0}.", remoteAttr.Server); } } Console.ReadLine(); } } }

      運行結果為:

      Creat this object on COSETTE。

      注意:在這個例子中的屬性類名具有Attribute後綴。但是,當我們將此屬性附著給類型或成員時卻不包括Attribute後綴。這是C#語言的設計者提供的簡單方式。當編譯器看到一個屬性被附著給一個類型或成員時,它會搜索具有指定屬性名的System.Attribute派生類。如果編譯器沒有找到匹配的類,它就在指定的屬性名後面加上Attribute,然後再進行搜索。因此,常見的使用做法是將屬性類名定義為以Attribute結尾,在使用時忽略名稱的這一部分。以下的代碼都采用這種命名方式。

      查詢方法屬性:

      在下面這個例子中,我們使用屬性將方法定義為可事務化的方法,只要存在TransactionableAttribute屬性,代碼就知道具有這個屬性的方法可以屬於一個事務。

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 using System; using System.Reflection; namespace MethodAttribs { public class TransactionableAttribute : Attribute { public TransactionableAttribute() { } } class SomeClass { [Transactionable] public void Foo() {} public void Bar() {} [Transactionable] public void Goo() {} } class Test { [STAThread] static void Main(string[] args) { Type type = Type.GetType("MethodAttribs.SomeClass"); foreach (MethodInfo method in type.GetMethods()) { foreach (Attribute attr in method.GetCustomAttributes(true)) { if (attr is TransactionableAttribute) { Console.WriteLine( "{0} is transactionable.", method.Name); } } } Console.ReadLine(); } } }

      運行結果如下:

      Foo is transactionable.

      Goo is transactionable.

      查詢字段屬性:

      假設有一個類含有一些字段,我們希望將它們的值保存進注冊表。為此,可以使用以枚舉值和字符串為參數的構造器定義一個屬性,這個枚舉值代表正確的注冊表hive,字符串代表注冊表值名稱。在運行時可以查詢字段的注冊表鍵。

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 using System; using System.Reflection; namespace FieldAttribs { public enum RegHives { HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_CONFIG } public class RegKeyAttribute : Attribute { public RegKeyAttribute(RegHives Hive, String ValueName) { this.Hive = Hive; this.ValueName = ValueName; } protected RegHives hive; public RegHives Hive { get { return hive; } set { hive = value; } } protected String valueName; public String ValueName { get { return valueName; } set { valueName = value; } } } class SomeClass { [RegKey(RegHives.HKEY_CURRENT_USER, "Foo")] public int Foo;   public int Bar; } class Test { [STAThread] static void Main(string[] args) { Type type = Type.GetType("FieldAttribs.SomeClass"); foreach (FieldInfo field in type.GetFields()) { foreach (Attribute attr in field.GetCustomAttributes(true)) { RegKeyAttribute rka = attr as RegKeyAttribute; if (null != rka) { Console.WriteLine( "{0} will be saved in" + " {1}{2}", field.Name, rka.Hive, rka.ValueName); } } } Console.ReadLine(); } } }

      運行結果為:

      Foo will be saved in HKEY_CURRENT_USERFoo

      大家可以看到,用屬性來標注類、方法、字段,既可以把用戶的自定義信息附屬在實體上,又可以在運行時動態的查詢。下面我將講一些C#中默認的預定義屬性,見下表:

      預定義的屬性 有效目標 說明

      AttributeUsage Class 指定另一個屬性類的有效使用方式

      CLSCompliant 全部 指出程序元素是否與CLS兼容

      Conditional Method 指出如果沒有定義相關聯的字符串,編譯器就可以忽略對這個方法的任何調用

      DllImport Method 指定包含外部方法的實現的DLL位置

      STAThread Method(Main) 指出程序的默認線程模型為STA

      MTAThread Method(Main) 指出程序的默認模型為多線程(MTA)

      Obsolete 除了Assembly、Module、Parameter和Return 將一個元素標示為不可用,通知用戶此元素將被從未來的產品

      ParamArray Parameter 允許單個參數被隱式地當作params(數組)參數對待

      Serializable Class、Struct、enum、delegate 指定這種類型的所有公共和私有字段可以被串行化

      NonSerialized Field 應用於被標示為可串行化的類的字段,指出這些字段將不可被串行化

      StructLayout Class、struct 指定類或結構的數據布局的性質,比如Auto、Explicit或sequential

      ThreadStatic Field(靜態) 實現線程局部存儲(TLS)。不能跨多個線程共享給定的靜態字段,每個線程擁有這個靜態字段的副本

      下面介紹幾種常用的屬性

      1.[STAThread]和[MTAThread]屬性

      ?

    1 2 3 4 5 6 7 class Class1 { [STAThread] Static void Main( string[] args ) { } }

      使用STAThread屬性將程序的默認線程模型指定為單線程模型。注意,線程模型只影響使用COM interop的應用程序,將這個屬性應用於不使用COM interop的程序將不會產生任何效果。

      2. AttributeUsage屬性

      除了用於標注常規C#類型的自定義屬性以外,還可以使用AttributeUsage屬性定義你使用這些屬性的方式。文件記錄的AttributeUsage屬性調用用法如下:

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [AttributeUsage( validon , AllowMutiple = allowmutiple , Inherited = inherited )] Validon參數是AttributeTargets類型的,這個枚舉值的定義如下: public enum AttributeTargets { Assembly = 0x0001, Module = 0x0002, Class = 0x0004, Struct = 0x0008, Enum = 0x0010, Constructor = 0x0020, Method = 0x0040, Property = 0x0080, Field = 0x0100, Event = 0x200, Interface = 0x400, Parameter = 0x800, Delegate = 0x1000, All = Assembly | Module | Class | Struct | Enum | Constructor| Method | Property| Filed| Event| Interface | Parameter | Deleagte , ClassMembers = | Class | Struct | Enum | Constructor | Method | Property | Field | Event | Delegate | Interface }

      AllowMultiple決定了可以在單個字段上使用某個屬性多少次,在默認情況下,所有的屬性都是單次使用的。示例如下:

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 [AttributeUsage( AttributeTargets.All , AllowMultiple = true )] public class SomethingAttribute : Attribute { public SomethingAttribute( string str ) { } } //如果AllowMultiple = false , 此處會報錯 [Something(“abc”)] [Something(“def”)] class Myclass { }

      Inherited參數是繼承的標志,它指出屬性是否可以被繼承。默認是false。

      Inherited AllowMultiple 結果

      true false 派生的屬性覆蓋基屬性

      true false 派生的屬性和基屬性共存

      代碼示例:

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 using System; using System.Reflection; namespace AttribInheritance { [AttributeUsage( AttributeTargets.All, AllowMultiple=true, // AllowMultiple=false, Inherited=true )] public class SomethingAttribute : Attribute { private string name; public string Name { get { return name; } set { name = value; } } public SomethingAttribute(string str) { this.name = str; } } [Something("abc")] class MyClass { } [Something("def")] class Another : MyClass { } class Test { [STAThread] static void Main(string[] args) { Type type = Type.GetType("AttribInheritance.Another"); foreach (Attribute attr in type.GetCustomAttributes(true)) // type.GetCustomAttributes(false)) { SomethingAttribute sa = attr as SomethingAttribute; if (null != sa) { Console.WriteLine( "Custom Attribute: {0}", sa.Name); } }   } } }

      當AllowMultiple被設置為false時,結果為:

      Custom Attribute : def

      當AllowMultiple被設置為true時,結果為:

      Custom Attribute : def

      Custom Attribute : abc

      注意,如果將false傳遞給GetCustomAttributes,它不會搜索繼承樹,所以你只能得到派生的類屬性。

      3.Conditional 屬性

      你可以將這個屬性附著於方法,這樣當編譯器遇到對這個方法調用時,如果沒有定義對應的字符串值,編譯器就忽略這個調用。例如,以下方法是否被編譯取決於是否定義了字符串“DEGUG”:

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 [Condition(“DEBUG”) ] public void SomeDebugFunc() { Console.WriteLine(“SomeDebugFunc”); } using System; using System.Diagnostics; namespace CondAttrib { class Thing { private string name; public Thing(string name) { this.name = name; #if DEBUG SomeDebugFunc(); #else SomeFunc(); #endif } public void SomeFunc() { Console.WriteLine("SomeFunc"); } [Conditional("DEBUG")] [Conditional("ANDREW")] public void SomeDebugFunc() { Console.WriteLine("SomeDebugFunc"); } } public class Class1 { [STAThread] static void Main(string[] args) { Thing t = new Thing("T1"); } } }

      4. Obsolete 屬性

      隨著代碼不斷的發展,你很可以會有一些方法不用。可以將它們都刪除,但是有時給它們加上適當的標注比刪除它們更合適,例如:

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using System; namespace ObsAttrib { class SomeClass { [Obsolete("Don't use OldFunc, use NewFunc instead", true)] public void OldFunc( ) { Console.WriteLine("Oops"); }   public void NewFunc( ) { Console.WriteLine("Cool"); } } class Class1 { [STAThread] static void Main(string[] args) { SomeClass sc = new SomeClass(); sc.NewFunc(); // sc.OldFunc(); // compiler error } } }

      我們將Obsolete屬性的第二個參數設置為true,當調用時函數時編譯器會產生一個錯誤。

      E:InsideC#CodeChap06ObsAttribObsAttribClass1.cs(20): 'ObsAttrib.SomeClass.OldFunc()' 已過時: 'Don't use OldFunc, use NewFunc instead'

      5. DllImport和StructLayout屬性

      DllImport可以讓C#代碼調用本機代碼中的函數,C#代碼通過平台調用(platform invoke)這個運行時功能調用它們。

      如果你希望運行時環境將結構從托管代碼正確地編組現非托管代碼(或相反),那麼需要為結構的聲明附加屬性。為了使結構參數可以被正確的編組,必須使用StructLayout屬性聲明它們,指出數據應該嚴格地按照聲明中列出的樣子進行布局。如果不這麼做,數據將不能正確地被編組,而應用程序可能會出錯。

      ?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 using System; using System.Runtime.InteropServices; // for DllImport namespace nativeDLL { public class Test { // [DllImport ("user32.dll")] // all the defaults are OK [DllImport("user32", EntryPoint="MessageBoxA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern int MessageBoxA ( int h, string m, string c, int type); [StructLayout(LayoutKind.Sequential)] public class SystemTime { public ushort wYear; public ushort wMonth; public ushort wDayOfWeek; public ushort wDay; public ushort wHour; public ushort wMinute; public ushort wSecond; public ushort wMilliseconds; } [DllImport ("kernel32.dll")] public static extern void GetLocalTime(SystemTime st); [STAThread] public static void Main(string[] args) { MessageBoxA(0, "Hello World", "nativeDLL", 0); SystemTime st = new SystemTime(); GetLocalTime(st); string s = String.Format("date: {0}-{1}-{2}", st.wMonth, st.wDay, st.wYear); string t = String.Format("time: {0}:{1}:{2}", st.wHour, st.wMinute, st.wSecond); string u = s + ", " + t; MessageBoxA(0, u, "Now", 0); } } }

      6. 配件屬性

      當使用.NET產生任何類型的C#工程時,會自動的產生一個AssemblyInfo.cs源代碼文件以及應用程序源代碼文件。AssemblyInfo.cs中含有配件中代碼的信息。其中的一些信息純粹是信息,而其它信息使運行時環境可以確保惟一的命名和版本號,以供重用你的配件的客戶代碼使用。

      7. 上下文屬性

      .NET櫃架還提供了另一種屬性:上下文屬性。上下文屬性提供了一種截取機制,可以在類的實例化和方法調用之前和之後進行處理。這種功能用於對象遠程調用,它是從基於COM的系統所用的COM+組件服務和Microsoft Transaction Services(MTS)。

      希望本文所述對大家的C#程序設計有所幫助。

    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved