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


在上一章中,我們探究了 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 修飾的參數。

此文僅授權《NCC 開源社區》訂閱號發布


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM