特性(Attribute)是用於爲程序元素添加額外信息的一種機制。好比記錄文件修改時間或代碼做者、提示某方法已通過期、描述如何序列化數據等等。方法、變量、屬性、類、接口、結構體以及程序集等都是程序元素。數組
1.使用特性緩存
可使用特性標註一個方法已通過時,已經有新方法了,但舊方法仍可使用,當編譯器發現某段代碼調用了被ObsoleteAttribute 標識的程序元素時,就會產生一個警告或錯誤信息。.NET 約定,全部特性的名稱都以Attribute 結尾,但在使用時能夠省略後綴Attribute。.NET 中共預約義了200 多個特性,能夠爲代碼中的程序元素提供豐富的描述信息。CLR 經過這些描述信息控制程序元素的行爲特徵,好比方法是否過期、如何序列化數據、記錄文件修改時間、記錄代碼做者、爲代碼添加調試信息等。例如:框架
[Obsolete("該方法已通過時,請使用新方法NewDrawMyself()", false)]函數
public void DrawMyself() {…}工具
2.自定義特性性能
特性也是以類的方式實現的,一個特性就是一個類。如今來自定義一個特性類,自定義特性類都要繼承自系統的Attribute類。例如自定義一個描述動物生物學分類信息的特性類AnimalInfoAttribute:學習
class AnimalInfoAttribute:Attributeui
{ public AnimalInfoAttribute(string nameValue)this
{ Name = nameValue; }spa
public string Name { get; set; } //屬性:名稱
public string Phylum { get; set; } //屬性:門
public string Classis { get; set; }//屬性:綱
public string Familia { get; set; }//屬性:科
}
使用自定義特性:
[AnimalInfo("狼", Phylum = "脊索動物門", Classis = "哺乳綱", Familia = "犬科")]
class Wolf{}
特性做用於程序元素後,就成爲程序元素的一部分。查看與程序元素相關的特性信息須要使用Attribute類的GetCustomAttribute()方法。若是程序元素中有多個特性,可以使用Attribute 類的GetCustomAttributes()方法獲取全部特性。例如:
static void Main(string[] args)
{ //獲取Wolf 類中的AnimalInfoAttribute 特性
Attribute myAttribute =
Attribute.GetCustomAttribute(typeof(Wolf),typeof(AnimalInfoAttribute));
AnimalInfoAttribute wolfAttribute = myAttribute as AnimalInfoAttribute;
if (wolfAttribute == null)
{Console.WriteLine("Wolf 類中不存在特性。");}
else
{Console.WriteLine(wolfAttribute.Name + ":");
Console.WriteLine(wolfAttribute.Phylum);
Console.WriteLine(wolfAttribute.Classis);
Console.WriteLine(wolfAttribute.Familia);}}
自定義的特性本質上也是一個類,在定義它的過程當中,也能夠被其餘特性修飾,好比常常用AttributeUsageAttribute 特性修飾自定義的特性。
[AttributeUsage(AttributeTargets.All,Inherited = true, AllowMultiple = true)]
class AnimalInfoAttribute:Attribute{}
AttributeUsageAttribute 類是專門用於修飾特性的特性,用它能夠限定特性的某些重要行爲特徵。AttributeUsageAttribute 類有三個重要屬性:AttributeTargets、Inherited 和AllowMultiple。
AttributeTargets 是一個枚舉,用來規定特性可做用於哪些程序元素。AttributeTargets 的值能夠經過「或」操做組合起來,使自定義的特性能做用於多種程序元素,例如[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]。Inherited 屬性用來指示特性可否被派生類繼承,例如class GrayWolf : Wolf{},當修飾AnimalInfoAttribute 的AttributeUsageAttribute.Inherited 爲true 時,Wolf 類中的AnimalInfoAttribute 特性信息會被派生類GrayWolf 繼承。AttributeUsageAttribute 的AllowMultiple 屬性用於指示特性可否屢次做用於同一程序元素。當AllowMultiple 爲true 時,咱們能夠屢次用AnimalInfoAttribute 修飾Wolf 類,例如:
[AnimalInfo("狼", Phylum = "脊索動物門", Classis = "哺乳綱", Familia = "犬科")]
[AnimalInfo("Wolf",Phylum ="Chordata",Classis = "Mammals",Familia = "Canids")]
class Wolf{…}
3.程序集
程序集(Assembly)是組織程序的邏輯單元,編寫好的代碼最終都會被編譯器編譯爲若干個程序集。須要指出的是程序集只是邏輯上的劃分,一個程序集能夠只由一個文件組成,也可由多個文件組成。不論是單文件程序集仍是多文件程序集,它們都由固定的結構組成。
兩種最多見的程序集——可執行文件(.exe)和動態連接庫文件(.dll),動態連接庫分爲早期的「Win32 函數庫」、「COM」和最近的「.NET 框架類庫」三種,雖然擴展名同爲dll,但其含義不一樣,這裏主要學習.NET中的動態連接庫。
(1)建立Animals.dll程序集
這個程序中包含兩個程序集,一個可執行文件ZooMap.exe,另外一個是動態連接庫Animals.dll。ZooMap.exe 是主程序,它調用Animals.dll 中的類來繪製動物圖像,而Animals.dll 中則包含了幾種和動物相關的類,爲主程序提供支持。
(2)建立ZooMap.exe程序集
建立一個名爲ZooMap的Windows應用程序解決方案,默認就會有一個ZooMap項目,而後再建立一個Animals的項目,此項目下編寫5個動物類,繼承通用接口IAnimal,均實現其ShowAnimal(Graphics g,int x,int y) 方法,而後將項目生成Animals.dll。最後在ZooMap項目將Animals.dll引用進來,再編寫窗體程序,在窗體中添加5個按鈕,分別爲按鈕添加事件處理程序,調用不一樣動物的ShowAnimal方法,顯示動物。至此在ZooMap的Debug文件夾中,即可以看到剛剛作好的兩個程序集ZooMap.exe和Animals.dll。
ShowAnimal(Graphics g,int x,int y)
{Bitmap animalImage = new Bitmap(Resource.Panda); //這裏以Panda爲例
g.DrawImage(animalImage , x, y);}
(3)程序集的結構
任何事物都由必定的結構組成,程序集也不例外,它由程序集元數據、類型元數據、MSIL 代碼和資源四部分組成。
①程序集元數據,程序集元數據也叫清單,它記錄了程序集的許多重要信息,是程序集進行自我說明的核心文檔。當程序運行時,CLR 經過這份清單就能獲取運行程序集所必需的所有信息。清單中主要主要包含以下信息:標識信息(包括程序集的名稱、版本、文化和公鑰等);文件列表(程序集由哪些文件組成);引用程序集列表(該程序集所引用的其餘程序集);一組許可請求(運行這個程序集須要的許可)。
②類型元數據,類型元數據列舉了程序集中包含的類型信息,詳細說明了程序集中定義了哪些類,每一個類包含哪些屬性和方法,每一個方法有哪些參數和返回值類型,等等。
③MSIL代碼,程序集元數據和類型元數據只是一些輔助性的說明信息,它們都是爲描述MSIL代碼而存在的。MSIL 代碼是程序集的真正核心部分,正是它們實現了程序集的功能。好比在「Animals」項目中,五個動物類的C#代碼最終都被轉換爲MSIL 代碼,保存在程序集Animals.dll 中,當運行程序時,就是經過這些MSIL 代碼繪製動物圖像的。
④資源,程序集中還可能包含圖像、圖標、聲音等資源。
總之,程序集是自我說明的,這些自我說明文檔提供了運行程序集所必須的信息,這樣咱們沒必要依靠註冊表等外部信息就能運行程序。全部的信息存於一處,這種設計方式也大大簡化了程序的安裝過程。
須要澄清的是,命名空間與程序集並不老是一一對應的。命名空間能夠看做類名的擴展,一個程序集能夠包含多個命名空間,一個命名空間也能夠分佈在多個程序集中。
(4)私有程序集和共享程序集
私有程序集是僅供單個軟件使用的程序集,安裝很簡單,只需把私有程序集複製到軟件包所在文件夾中便可。而那些被不一樣軟件共同使用的程序就是共享程序集,.NET類庫的程序集就是共享程序集,共享程序集爲不一樣的程序所共用,因此它的部署就不像私有程序集那麼簡單,必須考慮命名衝突和版本衝突等問題。解決這些問題的辦法是把共享程序集放在系統的一個特定文件夾內,這個特定文件夾稱爲全局程序集高速緩存(GAC)。這個過程可用專門的.NET 工具完成。
(5)程序集的特性
右擊「bin\Debug」目錄下的可執行文件ZooMap.exe,選擇「屬性」,能夠看到程序集相關的信息,以下圖所示,這些信息主要定義在「AssmeblyInfo.cs」文件中,以下圖所示。
打開「AssmeblyInfo.cs」文件,會看到以下的代碼:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有關程序集的常規信息經過下列屬性集控制,更改這些屬性值可修改
//與程序集關聯的信息。
[assembly: AssemblyTitle("ZooMap")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Clear Studio")]
[assembly: AssemblyProduct("ZooMap")]
[assembly: AssemblyCopyright("版權全部 (C) Clear Studio 2008")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 將 ComVisible 設置爲 false 使此程序集中的類型對 COM 組件不可見。若是須要從 COM 訪問此程序集中的類型,則將該類型上的 ComVisible 屬性設置爲 true。
[assembly: ComVisible(false)]
// 若是此項目向 COM 公開,則下列 GUID 是用於類型庫的 ID
[assembly: Guid("816a1507-8ca5-438d-87b4-9f3bef5b2481")]
// 程序集的版本信息由下面四個值組成:主版本、次版本、內部版本號、修訂號
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
程序集的屬性信息是由特性實現的,與普通特性的不一樣的是,描述程序集的特性前要添加前綴「assembly:」。與程序集相關的一些特性以下表所示:
4.反射
經過.NET 框架的反射機制(Reflection),能夠輕鬆的獲取類型和程序集的信息。.NET 在System.Reflection 命名空間中定義了一系列反射類,它們與System 命名空間中的Type 類一塊兒提供了反射功能。利用後二者來獲取前者相關信息。
(1)獲取類型信息(Type類)
經過 Type 類提供的方法和屬性獲取某個數據類型的相關信息,例如:
using System.Reflection;
static void Main(string[] args)
{//獲取類型
Type type = Type.GetType("System.Int32");
//輸出類型的相關信息
Console.WriteLine(" 類型名:" + type.Name);
Console.WriteLine(" 類的全名:" + type.FullName);
Console.WriteLine(" 命名空間名:" + type.Namespace);
Console.WriteLine(" 程序集名:" + type.Assembly.GetName().Name);
Console.WriteLine(" 模塊名:" + type.Module);
Console.WriteLine(" 基類名:" + type.BaseType);
Console.WriteLine(" 運行時映射的類名:" + type.UnderlyingSystemType);
Console.WriteLine(" 是不是類:" + type.IsClass);
Console.WriteLine(" 是不是接口:" + type.IsInterface);
Console.WriteLine(" 是不是抽象類:" + type.IsAbstract);
Console.WriteLine(" 是不是數組:" + type.IsArray);
Console.WriteLine(" 是不是值類型:" + type.IsValueType);
Console.WriteLine(" 是不是基本類型:" + type.IsPrimitive);
//獲取並輸出類的成員
MemberInfo[] members = type.GetMembers();
Console.WriteLine(" 類的公共成員:");
foreach (MemberInfo member in members)
{Console.WriteLine(" :" + member.MemberType + ":" + member.Name);}
}
經過 MemberInfo 等類的屬性和方法就能夠獲取相應成員的具體信息。
(2)獲取程序集信息(Assembly類)
System.Reflection 命名空間的Assembly 類是爲程序集設計的,經過它的屬性和方法,能夠獲取程序集的元數據信息。獲取程序集的元數據以前,須要先用Assembly對象的Load()方法(或LoadFile()、LoadFrom())把程序集加載到內存中。新建一個項目,並把「Animals.dll」動態連接庫添加到項目中,而後經過下面的代碼就可獲取程序集的相關信息。
using System.Reflection;
static void Main(string[] args)
{ //加載程序集
Assembly assembly = Assembly.LoadFrom("Animals.dll");
Console.WriteLine("程序集全名: " + assembly.FullName);
Console.WriteLine("程序集版本: " + assembly.GetName().Version);
Console.WriteLine("公共語言運行時版本: " + assembly.ImageRuntimeVersion);
Type[] types = assembly.GetTypes();//獲取並輸出程序集中的類型
Console.WriteLine("\n 程序集中的類型:");
foreach (Type type in types)
{Console.WriteLine(type);} }
(3)動態加載類型
之前編寫的程序中,對象的類型在編譯階段就已經肯定下來了,然而在某些問題中,對象的類型在運行時才能肯定,須要咱們動態的加載類型。利用反射技術能夠輕鬆實現動態加載類型。爲了實現上述功能,這裏建立了一個項目,添加了Animals.dll引用,而後在Form1類中編寫一個名爲DynamicallyDisplay()的方法,傳給它一個表明動物名稱的參數後,它將動態加載參數所指定的類,並進行繪圖。
①方法一:利用接口
using Animals
//在Form1類中添加下列方法,繪圖方法(利用動態加載的類)
public void DynamicallyDisplay(string typeName)
{ Graphics graphics = this.CreateGraphics();
Assembly assembly = Assembly.LoadFrom("Animals.dll");//動態加載程序集
Type AnimalType = assembly.GetType(typeName); //獲取動物類型
//建立動物實例
IAnimal someone = (IAnimal)Activator.CreateInstance(AnimalType,null);
someone.ShowAnimal(graphics, 75, 50); //調用對象
}
//下拉式列表框的事件處理程序(響應用戶點擊)
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{ string typeName = "Animals." + comboBox.SelectedItem;
DynamicallyDisplay(typeName); }
注意,雖然AnimalType 包含指定動物類的所有信息,但它仍然是一個Type 類的對象,並非動物類自己。因此不能經過new 運算符建立對象,而須要經過System.Activator 類的CreateInstance()方法建立對象。而且,,經過CreateInstance()方法獲得的對象爲Object型,須要強制轉換爲IAnimal 型才能調用ShowAnimal()方法。
②方法二:利用Invoke()方法
利用了全部動物都實現了 IAnimal 接口這一特徵,若是咱們前面沒有定義IAnimal 接口該怎麼辦呢?不用怕,咱們能夠經過Type 類的GetMethod()方法獲取ShowAnimal()方法的信息。
//繪圖方法(利用動態加載的類)
public void DynamicallyDisplay(string typeName)
{Assembly assembly = Assembly.LoadFrom("Animals.dll");
Type AnimalType = assembly.GetType(typeName);
object someone = Activator.CreateInstance(AnimalType); //建立動物實例
//獲取類型中的ShowAnimal()方法
MethodInfo methodInfo = AnimalType.GetMethod("ShowAnimal");
//建立方法ShowAnimal()的參數並調用該方法
Graphics graphics=this.CreateGraphics();
object[] args = new object[] {graphics, 75, 50 };
methodInfo.Invoke(someone, args); }
}
③方法三:利用dynamic關鍵字
//注意添加命名空間
using Animals
//在Form1 類中添加下列方法
//繪圖方法(利用動態加載的類)
public void DynamicallyDisplay(string typeName)
{Graphics graphics = this.CreateGraphics();
Assembly assembly = Assembly.LoadFrom("Animals.dll");
Type AnimalType = assembly.GetType(typeName);
//建立動物實例(仔細觀察這一句和方法一有和不一樣)
dynamic someone = Activator.CreateInstance(AnimalType);
someone.ShowAnimal(graphics, 75, 50); //調用對象
}
dynamic關鍵字是C#4.0 提供的新功能,用它聲明的引用符能夠接受任何類型的對象, 建立動物實例這條語句中沒有像方法一那樣進行強制類型轉換,如今你知道爲何了嗎?這是由於用dynamic 聲明的someone 能夠接受任何類型的對象。在方法三中,對象someone 在編譯階段是不知道其類型的,它繞過了類型檢查,直到程序運行時才肯定someone 的類型。
dynamic 關鍵字更經常使用於處理來自外界(COM、DLR、HTML DOM、XML、IronPython等)的對象,在這些時候,你極可能不能肯定這些對象的具體類型而僅僅知道它的一些屬性和方法,你只想調用須要的方法,至於操做對象是什麼類型,並不關心。因爲C#如今有了dynamic關鍵字就簡單多了,編譯器不對dynamic 聲明的對象進行類型檢查,只需直接調用你所須要的方法就好了,避免了大量的類型轉換工做,從而獲得乾淨清爽的代碼。