天天看點

差別C#中的兩個屬性(Property和Attribute)

差別C#中的兩個屬性(Property和Attribute)

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

Property比較簡單,就是我們常用的get和set,主要用于為類中的private和protected變量提供讀取和設定的接口。關于Property請參看我的一篇文章:

<a href="http://blog.csdn.net/tjvictor/archive/2006/06/23/824617.aspx">http://blog.csdn.net/tjvictor/archive/2006/06/23/824617.aspx</a>

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)。在定義屬性時你可以做許許多多的事情,下面我們看看如何在運作時查詢屬性。要想在運作時查詢類型或成員所附着的屬性,必須使用反射,請參見我的另一篇關于反射的簡單文章

<a href="http://blog.csdn.net/tjvictor/archive/2007/01/24/1492079.aspx">http://blog.csdn.net/tjvictor/archive/2007/01/24/1492079.aspx</a>

查詢類屬性:

假設你希望定義一個屬性,這個屬性定義了将在其上建立對象的遠端伺服器。如果沒有這個屬性,就要把此資訊儲存在一個常量中或是一個應用程式的資源檔案中。通過使用屬性,隻需用以下方法标注出類的遠端伺服器名即可:

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.Reflection;

namespace MethodAttribs

public class TransactionableAttribute : Attribute

public TransactionableAttribute()

class SomeClass

[Transactionable]

public void Foo()

{}

public void Bar()

public void Goo()

Type type = Type.GetType("MethodAttribs.SomeClass");

foreach (MethodInfo method in type.GetMethods())

method.GetCustomAttributes(true))

if (attr is TransactionableAttribute)

"{0} is transactionable.",

method.Name);

運作結果如下:

Foo is transactionable.

Goo is transactionable.

查詢字段屬性:

假設有一個類含有一些字段,我們希望将它們的值儲存進系統資料庫。為此,可以使用以枚舉值和字元串為參數的構造器定義一個屬性,這個枚舉值代表正确的系統資料庫hive,字元串代表系統資料庫值名稱。在運作時可以查詢字段的系統資料庫鍵。

namespace FieldAttribs

[RegKey(RegHives.HKEY_CURRENT_USER, "Foo")]

public int Foo;

public int Bar;

Type type = Type.GetType("FieldAttribs.SomeClass");

foreach (FieldInfo field in type.GetFields())

field.GetCustomAttributes(true))

RegKeyAttribute rka =

attr as RegKeyAttribute;

if (null != rka)

"{0} will be saved in"

+ " {1}\\\\{2}",

field.Name,

rka.Hive,

rka.ValueName);

運作結果為:

Foo will be saved in HKEY_CURRENT_USER\\Foo

大家可以看到,用屬性來标注類、方法、字段,既可以把使用者的自定義資訊附屬在實體上,又可以在運作時動态的查詢。下面我将講一些C#中預設的預定義屬性,見下表:

  預定義的屬性

  有效目标

  說明

  AttributeUsage

  Class

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

  CLSCompliant

  全部

  指出程式元素是否與CLS相容

  Conditional

  Method

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

  DllImport

  指定包含外部方法的實作的DLL位置

  STAThread

  Method(Main)

  指出程式的預設線程模型為STA

  MTAThread

  指出程式的預設模型為多線程(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

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

  派生的屬性覆寫基屬性

  派生的屬性和基屬性共存

代碼示例:

namespace AttribInheritance

[AttributeUsage(

AttributeTargets.All,

AllowMultiple=true,

// AllowMultiple=false,

Inherited=true

)]

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

Type type =

Type.GetType("AttribInheritance.Another");

// type.GetCustomAttributes(false))

SomethingAttribute sa =

attr as SomethingAttribute;

if (null != sa)

"Custom Attribute: {0}",

sa.Name);

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

Custom Attribute : def

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

Custom Attribute : abc

注意,如果将false傳遞給GetCustomAttributes,它不會搜尋繼承樹,是以你隻能得到派生的類屬性。

3.Conditional 屬性

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

[Condition(“DEBUG”) ]

public void SomeDebugFunc()

Console.WriteLine(“SomeDebugFunc”);

using System.Diagnostics;

namespace CondAttrib

class Thing

public Thing(string name)

this.name = name;

#if DEBUG

SomeDebugFunc();

#else

SomeFunc();

#endif

public void SomeFunc()

{ Console.WriteLine("SomeFunc"); }

[Conditional("DEBUG")]

[Conditional("ANDREW")]

{ Console.WriteLine("SomeDebugFunc"); }

public class Class1

Thing t = new Thing("T1");

4. Obsolete 屬性

随着代碼不斷的發展,你很可以會有一些方法不用。可以将它們都删除,但是有時給它們加上适當的标注比删除它們更合适,例如:

namespace ObsAttrib

[Obsolete("Don't use OldFunc, use NewFunc instead", true)]

public void OldFunc( ) { Console.WriteLine("Oops"); }

public void NewFunc( ) { Console.WriteLine("Cool"); }

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)這個運作時功能調用它們。

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

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);

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)。