區別C#中的兩個屬性(Property和Attribute)
2012-05-10 14:19:21| 分類: 技術類 | 標籤:c# property attribut |字型大小大中小 訂閱
區別C#中的兩個屬性(Property和Attribute)
在C#中有兩個屬性,分別為Property和Attribute,兩個的中文意思都有特性、屬性之間,但是用法上卻不一樣,為了區別,本文暫把Property稱為特性,把Attribute稱為屬性。
Property比較簡單,就是我們常用的get和set,主要用於為類中的private和protected變數提供讀取和設定的介面。關於Property請參看我的一篇文章:
http://blog.csdn.net/tjvictor/archive/2006/06/23/824617.aspx
Attribute才是本文的主角,把它稱為屬性我覺得很恰當。屬性的意思就是附屬於某種事物上的,用來說明這個事物的各種特徵的一種描述。而Attribute就是幹這事的。它允許你將資訊與你定義的C#類型相關聯,作為類型的標註。這些資訊是任意的,就是說,它不是由語言本身決定的,你可以隨意建立和關聯任何類型的任何資訊。你可以作用屬性定義設計時資訊和運行時資訊,甚至是運行時的行為特徵。關鍵在於這些資訊不僅可以被使用者取出來作為一種類型的標註,它更可以被編譯器所識別,作為編譯時間的一種附屬條件參加程式的編譯。
以下部分內容及代碼來源於《C#技術揭秘》(Inside C# Sencond Edition)
定義屬性:
屬性實際上是一個派生自System.Attribute基類的類。System.Attribute類含有幾個用於訪問和檢查自訂屬性的方法。儘管你有權將任何類定義為屬性,但是按照慣例來說,從System.Attribute衍生類別是有意義的。樣本如下:
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)。在定義屬性時你可以做許許多多的事情,下面我們看看如何在運行時查詢屬性。要想在運行時查詢類型或成員所附著的屬性,必須使用反射,請參見我的另一篇關於反射的簡單文章
http://blog.csdn.net/tjvictor/archive/2007/01/24/1492079.aspx
查詢類屬性:
假設你希望定義一個屬性,這個屬性定義了將在其上建立對象的遠程伺服器。如果沒有這個屬性,就要把此資訊儲存在一個常量中或是一個應用程式的資源檔中。通過使用屬性,只需用以下方法標註出類的遠程伺服器名即可:
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屬性,代碼就知道具有這個屬性的方法可以屬於一個事務。
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,字串代表註冊表值名稱。在運行時可以查詢欄位的註冊表鍵。
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_USER\\Foo
大家可以看到,用屬性來標註類、方法、欄位,既可以把使用者的自訂資訊附屬在實體上,又可以在運行時動態查詢。下面我將講一些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]屬性
class Class1
{
[STAThread]
Static void Main( string[] args )
{
}
}
使用STAThread屬性將程式的預設執行緒模式指定為單執行緒模式。注意,執行緒模式隻影響使用COM interop的應用程式,將這個屬性應用於不使用COM interop的程式將不會產生任何效果。
2. AttributeUsage屬性
除了用於標註常規C#類型的自訂屬性以外,還可以使用AttributeUsage屬性定義你使用這些屬性的方式。檔案記錄的AttributeUsage屬性調用用法如下:
[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決定了可以在單個欄位上使用某個屬性多少次,在預設情況下,所有的屬性都是單次使用的。樣本如下:
[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 |
派生的屬性和基屬性共存 |
程式碼範例:
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”:
[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 屬性
隨著代碼不斷的發展,你很可以會有一些方法不用。可以將它們都刪除,但是有時給它們加上適當的標註比刪除它們更合適,例如:
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#\Code\Chap06\ObsAttrib\ObsAttrib\Class1.cs(20): 'ObsAttrib.SomeClass.OldFunc()' 已淘汰: 'Don't use OldFunc, use NewFunc instead'
5. DllImport和StructLayout屬性
DllImport可以讓C#代碼調用機器碼中的函數,C#代碼通過平台叫用(platform invoke)這個運行時功能調用它們。
如果你希望運行時環境將結構從Managed 程式碼正確地編組現Unmanaged 程式碼(或相反),那麼需要為結構的聲明附加屬性。為了使結構參數可以被正確的編組,必須使用StructLayout屬性聲明它們,指出資料應該嚴格地按照聲明中列出的樣子進行布局。如果不這麼做,資料將不能正確地被編組,而應用程式可能會出錯。
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)。