什麼是Attribute,Attribute的作用是什麼
在代碼中,有時需要攜帶一些額外的信息。可以限定代碼的運行範圍,決定代碼是否對外可見;也可以是一些解釋性的信息,保證程序的正常運行。而我們最常見、最常用的Attribute就是public、private、static之類,是不是有點小驚訝。而Attribute的作用就類似public、private一樣,將附加信息與目標元素關聯起來。因此更通俗的說法,Attribute是一種代碼與數據的關聯方式。有點類似加了葡萄乾或其他佐料的曲奇餅乾。
Attribute的代碼定義
MSDN中Attribute的定義如下
[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple=false, Inherited=true)]
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.None)]
[System.Runtime.InteropServices.ComVisible(true)]
public abstract class Attribute : System.Runtime.InteropServices._Attribute
由代碼可知,Attribute是一個類。其實現了_Attribute接口,而該接口派生自:System.Attribute。
Attribute怎麼用
可能在工作中,用的最多的Attribute就是DllImport了,如下所示。
[DllImport(passDllPath, EntryPoint = "MDC_DoSetDrug", CharSet = CharSet.Ansi)]
public static extern int MDC_DoSetDrug(string pcDrugUniqueCode, string pcDrugName);
Attribute可以用在什麼地方
assembly, class, constructor, delegate, enum, event, field, interface, method, portable executable file module, parameter, property, return value, struct, or another attribute
基本上平時見到的都可以用
Attribute使用過程中的注意點
1、Attribute是一個類,因此使用時,需要向其傳遞構造器必須的參數,以及初始化一些屬性或設定。構造器的必須參數,被稱之爲“定位參數”(Positional Parameter),順手初始化的屬性或公共字段的參數,稱之爲“命名參數”(Named Parameter)。前者是必須,後者是選擇項。在VS使用中,智能提醒如下所示。裏面明確的指出,哪些是必填的定位參數,哪些是選填的命名參數。
2、可以將多個Attribute用於單個目標元素。Attribute的先後順序無所謂。多個Attribute可以放在一箇中括號內,也可以多個。另外,若Attribute的構造器不需要參數,則Attribute後面的圓括弧也可以刪除。舉例,以下都是相同的:
[Serializable][Flags]
[Serializable,Flags]
[SerializableAttribute,FlagsAttribute]
[FlagsAttribute(), SerializableAttribute()]
Attribute如何在項目中使用
使用.NET中自定義的Attribute
(一)常用的Attribute有:
1、DllImport應用於方法,是指告訴CLR,方法的實現位於制定路徑的**非託管**DLL中。
2、Flags應用於枚舉類型。告訴CLR,將枚舉類型作爲一個位標誌位集合使用
3、Serializable應用於類型。告訴序列格式化器,一個實例的字段可以序列化和反序列化。
….
具體詳細的可以參見:MSDN上Attribute的介紹內容
(二)使用時的注意事項或語法
在Attribute使用前,需要有一些前綴。例如:[method:DllImport(“…..”)],有時,前綴可以省略,編譯器會自動判斷。但有一些不能省略。以下是Attribute的語法。下面代碼的帶有“//前綴必須”代表不能刪除該前綴
using System;
[assembly:SomeAttr] //前綴必須
[module:SomeAttr] //前綴必須
namespace NETAttribute
{
[type:SomeAttr]
class AttributeGrammar<[typevar:SomeAttr] T>
{
[field:SomeAttr]
public int SomeFiled = 0;
[return: SomeAttr] //前綴必須
[method: SomeAttr]
public int SomeMethod([param:SomeAttr]int someParam)
{
return SomeParam
}
[property: SomeAttr]
public string SomeProp
{
[method: SomeAttr]
get { return null; }
}
[event: SomeAttr]
[field: SomeAttr] //前綴必須
[method: SomeAttr] //前綴必須
public event EventHandler SomeEvent;
}
}
自定義Attribute
另外,在項目中,需要攜帶額外的信息,但又不想從數據庫或配置文件中讀取,則可以試試自定義Attribute,用自定義Attribute的形式攜帶額外信息。
自定義Attribute的要求
需要滿足兩個條件
1、必須繼承system.Attribute抽象類
2、所有非抽象的Attribute至少包含一個公共構造器
另外,根據“行規”,自定義的Attribute要以“Attribute”字母結尾。
原則
Attribute的本質是一個類,但這個類只是提供一些基本的狀態信息,或用作邏輯狀態的一個容器。不應該在該類中實現複雜的方法、邏輯,更不應該提供方法、事件或者其他成員。若需要這些,請單獨編寫一個邏輯類(class文件)。不要把Attribute搞得複雜。
自定Attribute的使用
當實現自定義的Attribute後,就需要在程序中使用它。最主要的就是,獲取自定義Attribute的值。
如下所示:
using System;
using System.Reflection;
using System.Diagnostics;
[assembly:CLSCompliant(true)]
namespace NETAttribute
{
[Serializable]
[DefaultMember("Main")]
[DebuggerDisplay("Zheng",Name ="Lin",Target =typeof(Program))]
public class Program
{
[Conditional("Debug")]
[Conditional("Release")]
public void SomeMethod() { }
public Program() { }
[CLSCompliant(true)]
[STAThread]
public static void Main(string[] args) //此處Main方法需爲public,否則Main不會暴露,[DefaultMember("Main")]將無法應用到Main上
{
ShowAttributes(typeof(Program));
MemberInfo[] members = typeof(Program).FindMembers(MemberTypes.Constructor | MemberTypes.Method,
BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static,
Type.FilterName, "*");
foreach (MemberInfo item in members)
{
ShowAttributes(item);
}
Console.Read();
}
//獲取當前類型中的Attribute設定值
private static void ShowAttributes(MemberInfo attributeTarget)
{
//獲取目標上的Attribute
Attribute[] attributes = Attribute.GetCustomAttributes(attributeTarget);
//展示目標信息
Console.WriteLine("當前目標的名稱是{0},本目標上的Attribute有:{1}", attributeTarget.Name, (attributes.Length == 0 ? "無" : string.Empty));
//遍歷Attributes並將信息展示
foreach (Attribute item in attributes)
{
//顯示Attribute的類型
Console.WriteLine("Attribute的名稱是{0}",item.GetType().ToString());
if (item is DefaultMemberAttribute)
Console.WriteLine("MemberName={0}",((DefaultMemberAttribute)item).MemberName);
if (item is ConditionalAttribute)
Console.WriteLine("ConditionString={0}",((ConditionalAttribute)item).ConditionString);
if (item is CLSCompliantAttribute)
Console.WriteLine("IsCLSCompliant={0}",((CLSCompliantAttribute)item).IsCompliant);
DebuggerDisplayAttribute dda = item as DebuggerDisplayAttribute;
if (dda != null)
{
Console.WriteLine("Value={0},Name={1},Target={2}", dda.Value, dda.Name, dda.Target);
}
}
Console.WriteLine();
}
}
}
輸出結果爲:
使用反射查詢內容
上面的例子是使用Attribute靜態方法查詢相關方法,另外也可以使用反射實現。
重構ShowAttributes方法
/獲取當前類型中的Attribute設定值
private static void ShowAttributesBuReflection(MemberInfo attributeTarget)
{
//獲取目標上的Attribute
IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(attributeTarget);
//展示目標信息
Console.WriteLine("當前目標的名稱是{0},本目標上的Attribute有:{1}", attributeTarget.Name, (attributes.Count == 0 ? "無" : string.Empty));
//遍歷Attributes並將信息展示
foreach (CustomAttributeData item in attributes)
{
Type t = item.Constructor.DeclaringType;
Console.WriteLine("{0}", item.ToString());
Console.WriteLine("構造器={0}", item.Constructor);
IList<CustomAttributeTypedArgument> posArgs= item.ConstructorArguments;
Console.WriteLine("是否存在定位參數={0}",(posArgs.Count==0)?"否":string.Empty);
foreach (CustomAttributeTypedArgument m in posArgs)
{
Console.WriteLine("參數的類型:{0},參數值:{1}",m.ArgumentType,m.Value);
}
IList<CustomAttributeNamedArgument> namedArgs = item.NamedArguments;
Console.WriteLine("是否存在命名參數={0}", (namedArgs.Count == 0) ? "否" : string.Empty);
foreach (CustomAttributeNamedArgument m in namedArgs)
{
Console.WriteLine("參數名稱:{0},參數的類型:{1},參數值:{2}", m.MemberInfo.Name,
m.TypedValue.ArgumentType, m.TypedValue.Value);
}
}
Console.WriteLine();
}
運行結果如下:
總結
上面的兩個例子,明顯的能夠看到通過反射查詢和自身類靜態方法查詢的區別