《隨筆二十五》——C#中的 “ 反射和特性 ”

目錄

Type 類

使用 GetType方法 或使用typeof運算符 來 獲取 Type 對象 (482P)

什麼是特性

在程序集中使用C# 中已定義的特性 (485P)

使用 Obsolete 特性 來標識某段代碼是已過時的(485P)

使用 Conditional 特性 來禁止某些方法被調用(486P)

使用 CallerFilePath, CallerLineNumber, CallerMemberName 特性 來訪問文件路徑、代碼行數、調用成員名稱 (488P)

使用 DebuggerStepThrough 特性 (489P)

.NET中定義的重要特性列表 (490P)

爲一個程序結構應用多個特性 (490P)

將特性作用於 類的 字段、屬性上 (490P)

顯式地標記特性,然後應用到特定的目標程序結構上 (491P)

C# 語言定義的10個標準的特性目標列表(491P)

使用assembly 和 mode 名稱來設置全局特性 (491P)

聲明自定義特性 (492P)

特性的構造函數 (492P)

給特性的構造函數使用位置參數和命名參數 (493P)

使用 AttributeUsage 預定義特性應用到自定義特性上 (494P)

使用 IsDefined 方法 來訪問特性(496P)

使用 GetCustomAttributes 方法 來訪問特性(496P)


  • 有關程序及其類的數據稱爲元數據,並存儲在程序集中。
  • 程序可以在運行時查看其他程序集或其自身的元數據。當一個正在運行的程序查其自身的元數據 或者 其他程序的元數據時,它被稱爲反射。
  • 要使用反射,必須使用 System.Reflection 命名空間

Type 類


C#中的類型,比如系統預定義的類型, BCL 中的類型,自定義的類型。 每一種類型都有其自身的成員和 特性。

BCL聲明瞭一個名爲Type的抽象類,它被設計爲包含類型的特性。 使用Type類的對象可以獲取有關程序正在使用的類型的信息。

Type 類需要注意的有:

  • 對於程序中使用的每一種類型,CLR都會創建一個Type類的類型對象,其中包含關於該類型的信息。
  • 程序中使用的每種類型都與單獨的Type對象相關聯
  • 無論創建了多少個類型實例,只有一個 Type 對象與所有實例相關聯。

下圖顯示了一個運行中的程序,其中有兩個MyClass對象和一個OtherClass對象。注意,儘管MyClass有兩個實例,但是隻有一個Type對象表示它。

CLR爲程序中使用的每種類型實例化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)


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章