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

來源:互聯網
上載者:User

區別C#中的兩個屬性(PropertyAttribute)

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

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.