1. 程式人生 > >C#反射與特性(二):探究反射

C#反射與特性(二):探究反射

目錄

  • 1,反射的使用概述
  • 2,獲取 Type

在上一章中,我們探究了 C# 引入程式集的各種方法,這一章節筆者將探究 C# 中使用反射的各種操作和程式碼實踐。

1,反射的使用概述

1.1 什麼是反射

《C# 7.0 本質論》中:

反射是指對程式集中的元資料進行檢查的過程。

《C# 7.0 核心技術指南》中:

在執行時檢查並使用元資料和編譯程式碼的操作稱為反射。

Microsoft Docs :

反射提供描述程式集、模組和型別的物件。 可以使用反射動態地建立型別的例項,將型別繫結到現有物件,或從現有物件中獲取型別,然後呼叫其方法或訪問其欄位和屬性。

1.2 反射可以做什麼

《C# 7.0 本質論》、《C# 7.0 核心技術指南》、《Microsoft Docs》中,關於反射的作用,提綱整理如下:

  • 需要訪問程式元資料中的特性時;
  • 檢查和例項化程式集中的型別;
  • 在執行時構建新型別( Emit 技術);
  • 執行後期繫結,訪問在執行時建立的型別上的方法;
  • 訪問程式集中型別的元資料:
    其中包括像完整型別名和成員名這樣的構造,以及對一個構造進行修飾的任何特性。·使用元資料在執行時動態呼叫型別的成員,而不是使用編譯時繫結。
  • .NET通過 C# 語言提供的諸多服務(例如動態繫結、序列化、資料繫結和 Remoting)都是依託於元資料的:
    我們的應用程式可以充分地利用這些元資料,甚至可以通過自定義特性向元資料中新增資訊。我們甚至可以通過 System.Reflection.Emit 名稱空間中的類在執行時動態建立新的元資料和可執行IL(中間語言)指令。

1.3 Type 類

System.Type

表示型別宣告:類型別、介面型別、陣列型別、值型別、列舉型別、型別引數、泛型型別定義,以及開放或封閉構造的泛型型別。

Type 型別是反射技術的基礎,反射所有操作都離不開 Type。

1.4 反射使用歸類

C# 中,一個型別,可有以下元素組成:

型別名、建構函式/引數、方法/方法引數、欄位、屬性、基型別、繼承介面等。

而我們使用反射技術時,一般關注以下的資訊:

  • 型別的名稱

    Type.Name
  • 型別是不是 public

    Type.IsPublic
  • 型別的基型別

    Type.BaseType
  • 型別支援哪些介面

    Type.GetInterfaces()
  • 型別在哪個程式集中定義

    Type.Assembly
  • 型別的屬性、方法、欄位

    Type.GetProperties()
    Type.GetMethods()
    Type.GetFields()
  • 修飾型別的特性

    Type.GetCustomAttributes()

1.4 Type 一些常用屬性

            Type type = typeof(Program);
            Console.WriteLine(type.Namespace);
            Console.WriteLine(type.Name);   // 型別名稱
            Console.WriteLine(type.FullName); // 型別的完全限定名

            Assembly ass = type.Assembly;
            Console.WriteLine(ass.FullName);

輸出:

Mytest
Program
Mytest.Program
ConsoleApp4, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

下面,筆者將慢慢探究 C# 中關於反射的內容以及實踐驗證。

2,獲取 Type

2.1 獲取 Type 型別

獲取 Type 主要有兩種方法:

        Type type1 = typeof(MyClass);

        MyClass myClass = new MyClass();
        Type type2 = myClass.GetType();

typeof()靜態方法,可以直接獲取一個型別的 Type;

.GetType() 則是獲取一個例項的型別;

兩種方法想必各位以及司空見慣~

反射一般是編寫程式碼時,很多情況不能明確下才使用,一般結合程式集來獲取;

            Assembly ass = Assembly.LoadFrom(@"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\System.Console.dll");

            // 獲取當前控制檯程式的程式集,並且獲取 Console 這個型別
            // 注意,要使用完全限定名
            Type type = ass.GetType("System.Console");
            Type[] types = ass.GetTypes();
            Console.WriteLine("type: " + type.Name);

            // 獲取 System.Console.dll 中的所有型別
            foreach (var item in types)
            {
                Console.WriteLine("item: " + item.Name);
            }
            Console.ReadKey();

關於完全限定名,要根據實際情況填寫。

2.2 陣列 Type

獲取陣列的 Type ,這裡有兩種情況,一種是將型別生成型別的陣列,另一種是本身就是陣列型別;

例如說,

本身是 int 型別, 生成 int[] 陣列的 Type 型別;

本身是 int[] 型別,生成 int[] 陣列的 Type 型別;

生成陣列 Type

前者通過例項的 MakeArrayType() 方法生成,示例如下

            // int 生成 int[]
            Type typeArray_A = typeof(int).MakeArrayType();
            // int 生成 int[,] 多維陣列
            Type typeArray_B = typeof(int).MakeArrayType(2);

            Console.WriteLine(typeArray_A.Name);
            Console.WriteLine(typeArray_B.Name);
            Console.ReadKey();

輸出

Int32[]
Int32[,]

如果是交錯陣列 [][] 呢。。。怎麼建立。。。別急。。。後面有。。。

獲取陣列 Type

如果一個型別本身就是陣列呢?

            Type type = typeof(int[]);
            Console.WriteLine(type);

不需要進行任何操作。。。

獲取陣列的元素型別、維數

假如有個陣列 int[],我們要獲取陣列的元素型別 int,可以使用

            Type type = typeof(int[]);
            Type type1 = type.GetElementType();
            Console.WriteLine(type1.Name);

獲取一個多維陣列有維數

            Type type = typeof(int[,,,,,,,,,,]);
            Console.WriteLine(type.GetArrayRank());

輸出:11

            Type type = typeof(int[][][][][]);
            Console.WriteLine(type.GetArrayRank());

輸出:1

矩形陣列(交錯陣列)

int[,] 這樣的,稱為多維陣列;

int[][]這樣的,稱為矩形陣列、交錯陣列、鋸齒陣列(稱呼有點多)。

關於這方面的知識,可以參考筆者的另一篇文章:https://www.cnblogs.com/whuanle/p/9936047.html

Type 中,沒有建立交錯陣列的方式,因為實際上,交錯陣列是 陣列的陣列,例如 (int[]) 的陣列。

            // 先獲取一個型別
            Type arrayType = typeof(int[]);
            Type _Array = arrayType.MakeArrayType();
            Console.WriteLine(_Array.Name);

輸出

Int32[][]

2.3 巢狀型別

巢狀型別的使用跟正常的型別一致,巢狀型別的完全限定名稱由 {型別}+{巢狀型別} 組成,其它地方沒有什麼差異。

在 Program 建立一個類 MyClass

            Type type = typeof(Program.MyClass);
            Console.WriteLine(type.Namespace);
            Console.WriteLine(type.Name);
            Console.WriteLine(type.FullName);

            Assembly ass = type.Assembly;
            Console.WriteLine(ass.FullName);

輸出:

Mytest
MyClass
Mytest.Program+MyClass
ConsoleApp4, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

完全限定名稱:Mytest.Program+MyClass,由加號 + 連線。

2.4 泛型 Type

泛型資訊

先看以下例子

        Type typeA = typeof(Dictionary<,>);
        Type typeB = typeof(Dictionary<string, int>);
        Type typeC = typeof(List<>);
        Type typeD = typeof(List<string>);

然後列印 Name

            Console.WriteLine(typeA.Name);
            Console.WriteLine(typeB.Name);
            Console.WriteLine(typeC.Name);
            Console.WriteLine(typeD.Name);

輸出

Dictionary`2
Dictionary`2
List`1
List`1

列印 FullName

            Console.WriteLine(typeA.FullName);
            Console.WriteLine(typeB.FullName);
            Console.WriteLine(typeC.FullName);
            Console.WriteLine(typeD.FullName);

輸出

System.Collections.Generic.Dictionary`2

System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Int32, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

System.Collections.Generic.List`1

System.Collections.Generic.List`1[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

Dictionary2 ,這個 `後表示此泛型型別有多少個泛型引數。

使用 .Name 看不出引數以及引數型別;FullName 可以看到完整的引數。

泛型相關

Type 中,與 泛型 有關的函式如下:

System.Type 成員名稱 說明
IsGenericType 如果型別是泛型,則返回 true。
GetGenericArguments() 返回 Type 物件的陣列,這些物件表示為構造型別提供的型別實參或泛型型別定義的型別形參。
GetGenericTypeDefinition() 返回當前構造型別的基礎泛型型別定義。
GetGenericParameterConstraints() 返回表示當前泛型型別引數約束的 Type 物件的陣列。
ContainsGenericParameters() 如果型別或任何其封閉型別或方法包含未提供特定型別的型別引數,則返回 true。
GenericParameterAttributes() 獲取描述當前泛型型別引數的特殊約束的 GenericParameterAttributes 標誌組合。
GenericParameterPosition() 對於表示型別引數的 Type 物件,獲取型別引數在宣告其型別引數的泛型型別定義或泛型方法定義的型別引數列表中的位置。
IsGenericParameter 獲取一個值,該值指示當前 Type 是否表示泛型型別或方法定義中的型別引數。
IsGenericTypeDefinition 獲取一個值,該值指示當前 Type 是否表示可以用來構造其他泛型型別的泛型型別定義。 如果該型別表示泛型型別的定義,則返回 true。
DeclaringMethod() 返回定義當前泛型型別引數的泛型方法,如果型別引數未由泛型方法定義,則返回 null。
MakeGenericType() 替代由當前泛型型別定義的型別引數組成的型別陣列的元素,並返回表示結果構造型別的 Type 物件。
            Type typeA = typeof(Dictionary<,>);
            Type typeB = typeof(Dictionary<string, int>);
            Console.WriteLine(typeA.IsGenericMethodParameter +"|"+typeB.IsGenericMethodParameter);
            Console.WriteLine(typeA.IsGenericParameter + "|" + typeB.IsGenericParameter);
            Console.WriteLine(typeA.IsGenericType + "|" + typeB.IsGenericType);
            Console.WriteLine(typeA.IsGenericTypeDefinition + "|" + typeB.IsGenericTypeDefinition);
            Console.WriteLine(typeA.IsGenericTypeParameter + "|" + typeB.IsGenericTypeParameter);

輸出:

False|False
False|False
True|True
True|False
False|False

老實說,除了 IsGenericType,其它的我都不懂什麼意思。

GetGenericArguments() 可以獲取泛型的引數型別;

            Type type = typeof(Dictionary<string, int>);
            Type[] list = type.GetGenericArguments();
            foreach (var item in list)
                Console.WriteLine(item.Name);

輸出

String
Int32

2.5 方法的引數和 ref / out

建立一個型別

        public class MyClass
        {
            public void Test(string str, ref string a, out string b)
            {
                b = "666";
                Console.WriteLine(b);
            }
        }

獲取方法的引數列表

            // 獲取一個方法的引數列表
            ParameterInfo[] paramList = typeof(MyClass).GetMethod(nameof(MyClass.Test)).GetParameters();

            foreach (var item in paramList)
                Console.WriteLine(item);

輸出:

System.String str
System.String& a
System.String& b

如果引數型別後面有 & ,則代表是 ref 或 out 修飾的參