目錄
使用 GetType方法 或使用typeof運算符 來 獲取 Type 對象 (482P)
使用 Obsolete 特性 來標識某段代碼是已過時的(485P)
使用 Conditional 特性 來禁止某些方法被調用(486P)
使用 CallerFilePath, CallerLineNumber, CallerMemberName 特性 來訪問文件路徑、代碼行數、調用成員名稱 (488P)
使用 DebuggerStepThrough 特性 (489P)
顯式地標記特性,然後應用到特定的目標程序結構上 (491P)
使用assembly 和 mode 名稱來設置全局特性 (491P)
使用 AttributeUsage 預定義特性應用到自定義特性上 (494P)
使用 GetCustomAttributes 方法 來訪問特性(496P)
- 有關程序及其類的數據稱爲元數據,並存儲在程序集中。
- 程序可以在運行時查看其他程序集或其自身的元數據。當一個正在運行的程序查其自身的元數據 或者 其他程序的元數據時,它被稱爲反射。
- 要使用反射,必須使用 System.Reflection 命名空間
Type 類
C#中的類型,比如系統預定義的類型, BCL 中的類型,自定義的類型。 每一種類型都有其自身的成員和 特性。
BCL聲明瞭一個名爲Type的抽象類,它被設計爲包含類型的特性。 使用Type類的對象可以獲取有關程序正在使用的類型的信息。
Type 類需要注意的有:
- 對於程序中使用的每一種類型,CLR都會創建一個Type類的類型對象,其中包含關於該類型的信息。
- 程序中使用的每種類型都與單獨的Type對象相關聯
- 無論創建了多少個類型實例,只有一個 Type 對象與所有實例相關聯。
下圖顯示了一個運行中的程序,其中有兩個MyClass對象和一個OtherClass對象。注意,儘管MyClass有兩個實例,但是隻有一個Type對象表示它。
可以從Type對象中獲得關於類型的幾乎所有信息。 下標列出了 System.Type類部分成員
使用 GetType方法 或使用typeof運算符 來 獲取 Type 對象 (482P)
可以使用 GetType方法 或使用typeof運算符 來獲取 Type對象。 Type對象包含一個名爲 GetType 的方法,該方法返回對實例的Type對象的引用。 由於每個類型最終都是從 object 派生的,因此可以在任何類型的對象上調用GetType方法來獲取其Type對象。
namespace HelloWorld_Console
{
class BaseClass
{
public int BaseField = 0;
}
class DerivedClass:BaseClass
{
public int DerivedField = 0;
}
class Program
{
static void Main()
{
var bc = new BaseClass();
var dc = new DerivedClass();
BaseClass[] bca = new BaseClass[] { bc, dc };
foreach(var v in bca)
{
Type t = v.GetType(); // 獲取類型
WriteLine($"Object type: {t.Name}");
FieldInfo[] fi = t.GetFields(); // 獲取字段信息
foreach (var f in fi)
{
WriteLine($" Field: {f.Name}");
}
WriteLine();
}
}
}
}
您還可以使用 typeo f運算符來獲取 Type 對象。 只需提供類型的名稱作爲操作數,它就會返回對Type對象的引用,如下所示:
namespace HelloWorld_Console
{
class BaseClass
{
public int BaseField = 0;
}
class DerivedClass:BaseClass
{
public int DerivedField = 0;
}
class Program
{
static void Main()
{
Type tbc = typeof(DerivedClass); // Get the type.
Console.WriteLine("Result is {0}.", tbc.Name);
Console.WriteLine("It has the following fields:"); // Use the type.
FieldInfo[] fi = tbc.GetFields();
foreach (var f in fi)
Console.WriteLine(" {0}", f.Name);
}
}
}
什麼是特性
特性是一種語言結構,允許您將元數據添加到程序集中。 它是一種特殊類型的類,用於存儲有關程序結構的信息。
- 應用特性的程序結構稱爲其目標
- 設計用來獲取和使用元數據的程序,如對象瀏覽器,被稱爲特性的消費者。
- .Net 中預定義了一些特性,您還可以聲明自定義特性。
下圖給出了使用特性所涉及的組件的概述,並說明了關於它們的以下幾點:
- 我們在源代碼中將特性應用於程序結構。
- 編譯器獲取源代碼並且從特性產生元數據, 然後把元數據放到程序集中。
- 消費者程序可以訪問屬性的元數據以及程序其餘組件的元數據。 請注意,編譯器生成並使用屬性。
按照慣例,特性名稱使用Pascal大小寫,並以後綴 Attribute 結尾。 但是,在將特性應用於目標時,可以不使用後綴。 例如,對於特性SerializableAttribute 和 MyAttributeAttribute,在將它們應用於程序結構時,可以使用短名稱 Serializable 和 MyAttribute。
在程序集中使用C# 中已定義的特性 (485P)
特性的目的是告訴編譯器把程序結構的元數據,將其放到程序集中。您可以通過將特性應用於程序結構來實現這一點。
使用下面方法使用特性:
- 在結構前放置特性片段來應用特性
- 特性片段被方括號包圍,其中是特性名和特性的參數列表。
[ Serializable ] // 特性 public class MyClass { ... [ MyAttribute("Simple class", "Version 3.57") ] // 帶有參數的特性 public class MyOtherClass { ...
有關特性需要了解的重要事項如下:
- 大多數特性只針對直接跟隨在一個或多個特性片段後的結構起作用
- 應用了特性的結構稱爲被特性裝飾(decorated或adorned,兩者都應用得很普遍)。
使用 Obsolete 特性 來標識某段代碼是已過時的(485P)
namespace HelloWorld_Console { class Program { [Obsolete("Use method SuperPrintOut")] // 將特性應用到方法 static void PrintOut(string str) { WriteLine(str); } static void Main() { PrintOut("huangchengtao"); } } }
注意: 即使 該函數被標記爲 Obsolete,表示已過期。 但是Main 函數還是調用了該函數,並且輸出了相應的結果。 但是在輸出的過程中,編譯器會產生一個 CS0618 的警告信息,來指示出我們正在調用一個已過期的函數。
Obsolete 屬性還有第二個可選參數,此參數接受一個 bool 類型的值。 當爲true 時,當調用該代碼結構時,將發生錯誤, 而不是警告。 默認情況下,爲 false。
namespace HelloWorld_Console { class Program { [Obsolete("Use method SuperPrintOut",true)] // 將特性應用到方法 static void PrintOut(string str) { WriteLine(str); } static void Main() { PrintOut("huangchengtao"); } } }
使用 Conditional 特性 來禁止某些方法被調用(486P)
Conditional屬性允許您指示編譯器包含或排除特定方法的所有調用。 要使用Conditional屬性,請將其作用於方法聲明以及編譯符號作爲參數。
using System.Diagnostics;
namespace HelloWorld_Console
{
class Program
{
[Conditional("DoTrace")]
static void TraceMessage(string str)
{
WriteLine(str);
}
static void Test()
{
WriteLine("huangchengtao");
}
static void Main(string []args)
{
TraceMessage("在 Main 的開始");
WriteLine("Doing work in Main");
TraceMessage("End of Main");
Test();
}
}
}
使用 CallerFilePath, CallerLineNumber, CallerMemberName 特性 來訪問文件路徑、代碼行數、調用成員名稱 (488P)
這些屬性只能與方法上的可選參數一起使用
下面的代碼聲明瞭一個名爲MyTrace的方法, 它在三個可選參數上使用了這三個調用者信息特性。如果調用方法時顯式地提供了實參, 則會使用實參的值。但在下面所示的Main方法中調用時, 沒有顯式提供實現,,因此係統將會提供源代碼的文件路徑、調用該方法的代碼行數和調用該方法的成員名稱。
using System.Runtime.CompilerServices;
namespace HelloWorld_Console
{
class Program
{
public static void MyTrace(string message,
[CallerFilePath] string fileName = "",
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string callingMember = "")
{
Console.WriteLine("File: {0}", fileName);
Console.WriteLine("Line: {0}", lineNumber);
Console.WriteLine("Called From: {0}", callingMember);
Console.WriteLine("Message: {0}", message);
}
static void Main(string []args)
{
MyTrace("Simple message");
}
}
}
使用 DebuggerStepThrough 特性 (489P)
我們在單步調試代碼時, 常常希望調試器不要進入某些方法。我們只想單純的執行該方法, 然後繼續調試下一行。DebuggerStepThrough特性告訴調試器在執行目標代碼時不要進入該方法調試。
關於 DebuggerStepThrough 要注意以下兩點:
- 該特性位於 System.Diagnostics命名空間中;
- 該特性可用於類、結構、構造函數、方法 或 訪問器。
namespace HelloWorld_Console
{
class Program
{
int _x = 1;
int X
{
get { return _x; }
[DebuggerStepThrough] // 在調式時,不會進入set 訪問器中
set
{
_x = _x * 2;
_x += value;
}
}
public int Y { get; set; }
static void Main(string []args)
{
Program p = new Program();
p.IncrementFields();
p.X = 5;
Console.WriteLine("X = {0}, Y = {1}", p.X, p.Y);
}
[DebuggerStepThrough] // // 在調式時,不會進入該函數
void IncrementFields()
{
X++;
Y++;
}
}
}
.NET中定義的重要特性列表 (490P)
爲一個程序結構應用多個特性 (490P)
將特性作用於 類的 字段、屬性上 (490P)
顯式地標記特性,然後應用到特定的目標程序結構上 (491P)
C# 語言定義的10個標準的特性目標列表(491P)
使用assembly 和 mode 名稱來設置全局特性 (491P)
聲明自定義特性 (492P)
有關特性類的要點:
- 用戶自定義的特性類叫做自定義特性。
- 所有特性類都派生自 System.Attribute.
聲明一個特性類需要注意的有:
- 該類最好是一個 sealed 的 並且 繼承與 System.Attribute 的類
- 該類必須是以 Attribute 結尾的類名
特性只能包含以下公共成員:
- 字段、屬性、構造函數
特性的構造函數 (492P)
- 每一個特性都必須要有一個公共的構造函數,不管是隱式地還是顯示的。
- 特性的構造函數,可以被重載。
- 聲明構造函數時,必須使用類全名,包括後輟。
- 在應用特性時 , 構造函數的實參必須是在編譯期能確定值的常量表達式。
- 如果應用的特性構造函數沒有參數, 可以省略圓括號。
給特性的構造函數使用位置參數和命名參數 (493P)
使用 AttributeUsage 預定義特性應用到自定義特性上 (494P)
使用 IsDefined 方法 來訪問特性(496P)
我們可以使用Type對象的IsDefined方法來檢測某個特性是否應用到了某個類上。
使用 GetCustomAttributes 方法 來訪問特性(496P)