淺談.NET中的反射

来源:https://www.cnblogs.com/xiaomowang/archive/2019/11/29/11948998.html
-Advertisement-
Play Games

一、概述 1、通過反射可以提供類型信息,從而使得我們開發人員在運行時能夠利用這些信息構造和使用對象 2、反射機制允許程式在執行過程中動態地添加各種功能 二、運行時類型標識 1、運行時類型標誌(RTTI),可以在程式執行期間判斷對象類型。例如使用他能夠確切的知道基類引用指向了什麼類型對象。 2、運行時 ...


一、概述 

1、通過反射可以提供類型信息,從而使得我們開發人員在運行時能夠利用這些信息構造和使用對象

2、反射機制允許程式在執行過程中動態地添加各種功能

二、運行時類型標識

1、運行時類型標誌(RTTI),可以在程式執行期間判斷對象類型。例如使用他能夠確切的知道基類引用指向了什麼類型對象。

2、運行時類型標識,能預先測試某個強制類型轉換操作,能否成功,從而避免無效的強制類型轉換異常。

3、在C#中有三個支持RTTI的關鍵字:is、as、typeof。下麵一次介紹他們

is運算符:

通過is運算符,能夠判斷對象類型是否為特定類型,如果兩種類型時相同類型,或者兩者之間存在引用,裝箱拆箱轉換,則表明兩種類型時相容的。代碼如下:

 1         static void Main()
 2         {
 3             A a = new A();
 4             B b = new B();
 5             if (a is A)
 6             {
 7                 Console.WriteLine("a is an A");
 8             }
 9 
10             if (b is A)
11             {
12                 Console.WriteLine("b is an A because it is derived from");
13             }
14 
15             if (a is B)
16             {
17                 Console.WriteLine("This won't display,because a not derived from B");
18             }
19 
20             if (a is object)
21             {
22                 Console.WriteLine("a is an object");
23             }
24             Console.ReadKey();
25         }

結果:

 

 as運算符:

在運行期間執行類型轉換,並且能夠是的類型轉換失敗不拋出異常,而返回一個null值,其實as也可以看作一個is運算符的簡化備選方式,如下:

 

 1         static void Main()
 2         {
 3             A a = new A();
 4             B b = new B();
 5             if (a is B)
 6             {
 7                 b = (B) a;//由於a變數不是B類型,因此這裡將a變數轉換為B類型時無效的
 8             }
 9             else
10             {
11                 b = null;
12             }
13 
14             if (b==null)
15             {
16                 Console.WriteLine("The cast in b=(B)a is not allowed");
17             }
18             //上面使用as運算符,能夠把兩部分合二為一
19             b = a as B;//as運算符先檢查將之轉換類型的有效性,如果有效,則執行強類型轉換過程,這些都在這一句話完成
20             if (b==null)
21             {
22                 Console.WriteLine("The cast in b=(B)a is not allowed");
23             }
24             Console.ReadKey();
25         }

結果:

 

 typeof運算符:

as、is 能夠測試兩種類型的相容性,但大多數情況下,還需要獲得某個類型的具體信息。這就用到了typeof,他可以返回與具體類型相關的System.Type對象,通過System.Type對象可以去定此類型的特征。一旦獲得給定類型的Type對象,就可以通過使用對象定義的各自屬性、欄位、方法來獲取類型的具體信息。Type類包含了很多成元,在接下來的反射中再詳細討論。下麵簡單的演示Type對象,調用它的三個屬性。

 

 1         static void Main()
 2         {
 3             Type t = typeof(StringBuilder);
 4             Console.WriteLine(t.FullName);//FullName屬性返回類型的全稱
 5             if (t.IsClass)
 6             {
 7                 Console.WriteLine("is a Class");
 8             }
 9 
10             if (t.IsSealed)
11             {
12                 Console.WriteLine("is Sealed");
13             }
14             Console.ReadKey();
15         }

結果:

 

 

三、反射的核心類型:System.Type類

1、許多支持反射的類型都位於System.Reflection命名空間中,他們是.net Reflection API的一部分,所以再使用的反射的程式中一般都是要使用System.Reflection的命名空間。

2、System.Type類包裝了類型,因此是整個反射子系統的核心,這個類中包含了很多屬性和方法,使用這些屬性和方法可以再運行時得到類型的信息。

3、Type類派生於System.Reflection.MemberInfo抽象類

MemberInfo類中的只讀屬性

屬性

描述

Type DeclaringType

獲取聲明該成員的類或介面的類型

MemberTypes  MemberType

獲取成員的類型,這個值用於指示該成員是欄位、方法、屬性、事件、或構造函數

Int MetadataToken

獲取與特定元數據相關的值

Module Module

獲取一個代表反射類型所在模塊(可執行文件)的Module對象

String  Name

成員的名稱

Type  ReflectedType

反射的對象類型

請註意:

1、MemberType屬性的返回類型為MemberTypes,這是一個枚舉,它定義了用於表示不同成元的信息值,這些包括:MemberTypes.Constructor、MemeberTypes.Method、MemberTypes.Event、MemberTypes.Property。因此可以通過檢查MemberType屬性來確定成元的類型,例如在MenberType屬性的值為MemberTypes.Method時,該成員為方法

2、MemberInfo類還包含兩個與特性相關的抽象方法:

(1)GetCustomAttributes():獲得與主調對象相關的自定義特性列表。

(2)IsDefined():確定是否為主調對象定義了相應的特性。

(3)GetCustomeAttributesData():返回有關自定義特性的信息(特性稍後便會提到)

當然除了MemberInfo類定義的方法和屬性外,Type類自己也添加了許多屬性和方法:如下表(只列出一些常用的,太多二零,自己可以轉定義Type類看一下)

Type類定義的方法

方法

功能

ConstructorInfo[]  GetConstructors()

獲取指定類型的構造函數列表

EventInfo[]   GetEvents();

獲取指定類型的時間列

FieldInfo[]   GetFields();

獲取指定類型的欄位列

Type[]   GetGenericArguments();

獲取與已構造的泛型類型綁定的類型參數列表,如果指定類型的泛型類型定義,則獲得類型形參。對於正早構造的類型,該列表就可能同時包含類型實參和類型形參

MemberInfo[]   GetMembers();

獲取指定類型的成員列表

MethodInfo[]   GetMethods();

獲取指定類型的方法列表

PropertyInfo[]   GetProperties();

獲取指定類型的屬性列表

下麵列出Type類型定義的常用只讀屬性

Type類定義的屬性

屬性

功能

 Assembly   Assembly 

獲取指定類型的程式集

TypeAttributes   Attributes

獲取制定類型的特性

Type   BaseType

獲取指定類型的直接基類型

String  FullName

獲取指定類型的全名

bool   IsAbstract

如果指定類型是抽象類型,返回true

bool   IsClass

如果指定類型是類,返回true

string   Namespace

獲取指定類型的命名空間

四、使用反射

上面將的這些,都是為了使用反射做鋪墊的。

通過使用Type類定義的方法和屬性,我們能夠在運行時獲得類型的各種具體信息。這是一個非常強大的功能,我們一旦得到類型信息,就可以調用其構造函數、方法、屬性,可見,反射是允許使用編譯時不可用的代碼的。

由於Feflection API非常多,這裡不可能完整的介紹他們(這裡如果完整的介紹,據說要一本書,厚書)。但是Reflection API是按照一定邏輯設計的,因此,只要知道部分介面的使用方法,就可以舉一反三的使用剩餘的介面。

這裡我列出四種關鍵的反射技術:

1、獲取方法的信息

2、調用方法

3、構造對象

4、從程式集中載入類型

五、獲取方法的相關信息

一旦有了Type對象就可以使用GetMethodInfo()方法獲取此類型支持的所有方法列表。該方法返回一個MethodInfo對象數組,MethodInfo對象表述了主調類型所支持的方法,它位於System.Reflection命名空間中。MethodInfo類派生於MethodBase抽象類,而MethodBase類繼承了MemberInfo類,因此,我們能夠使用這三各類定義的屬性和方法。例如,使用Name屬性的到方法名,這裡有兩個重要的成員:

1、ReturnType屬性:為Type類型的對象,能夠提供方法的返回類型信息。

2、GetParameters()方法:返回參數列表,參數信息以數組的形式保存在PatameterInfo對象中。PatameterInfo類定義了大量表述參數信息的屬性和方法,這裡也累出兩個常用的屬性:Name(包含參數名稱信息的字元串),ParameterType(參數類型的信息)。

下麵代碼我將使用反射獲得類中的所支持的方法,還有方法的信息:

 

 1     class Program
 2     {
 3         static void Main()
 4         {
 5             //獲取描述MyClass類型的Type對象
 6             Type t = typeof(MyClass);
 7             Console.WriteLine($"Analyzing methods in {t.Name}");
 8             //MethodInfo對象在System.Reflection命名空間下
 9             MethodInfo[] mi = t.GetMethods();
10             foreach (var methodInfo in mi)
11             {
12                 //返回方法的返回類型
13                 Console.Write(methodInfo.ReturnType.Name);
14                 //返回方法的名稱
15                 Console.Write($" {methodInfo.Name} (");
16                 //獲取方法闡述列表並保存在ParameterInfo對象組中
17                 ParameterInfo[] pi = methodInfo.GetParameters();
18                 for (int i = 0; i < pi.Length; i++)
19                 {
20                     //方法的參數類型名稱
21                     Console.Write(pi[i].ParameterType.Name);
22                     //方法的參數名
23                     Console.Write($" {pi[i].Name}");
24                     if (i+1<pi.Length)
25                     {
26                         Console.Write(", ");
27                     }
28                 }
29 
30                 Console.Write(")");
31                 Console.Write("\r\n");
32                 Console.WriteLine("--------------------------");
33             }
34             Console.ReadKey();
35         }
36     }
37 
38     class MyClass
39     {
40         private int x;
41         private int y;
42 
43         public MyClass()
44         {
45             x = 1;
46             y = 1;
47         }
48 
49         public int Sum()
50         {
51             return x + y;
52         }
53 
54         public bool IsBetween(int i)
55         {
56             if (x < i && i < y)
57             {
58                 return true;
59             }
60 
61             return false;
62         }
63 
64         public void Set(int a, int b)
65         {
66             x = a;
67             y = b;
68         }
69 
70         public void Set(double a, double b)
71         {
72             x = (int)a;
73             y = (int)b;
74         }
75 
76         public void Show()
77         {
78             System.Console.WriteLine($"x:{x},y:{y}");
79         }
80     }

輸出結果:

 

 註意:這裡輸出的除了MyClass類定義的所有方法外,也會顯示object類定義的共有非靜態方法。這是因為C#中的所有類型都繼承於Object類。另外,這些信息是在程式運行時動態獲得的,並不需要知道MyClass類的定義

GetMethods()方法的另一種形式

這種形式可以指定各種標記,已篩選想要獲取的方法,他的通用形式為:MethodInfo[] GetMethods(BindingFlags bindingAttr)

BindingFlags是一個枚舉,枚舉值有(很多,這裡只列出5個常用的吧)

(1)DeclareOnly:僅獲取指定類定義的方法,而不獲取所繼承的方法

(2)Instance:獲取實例方法

(3)NonPublic:獲取非公有方法

(4)Public:獲取共有方法

(5)Static:獲取靜態方法

GetMethods(BindingFlags bindingAttr)這個方法,參數可以使用 or 把兩個或更多標記連接在一起,實際上至少要有Instance(或 Static)與Public(或 NonPublic)標記,否則將不會獲取任何方法。下我們就寫一個示例來演示一下。

 1     class Program
 2     {
 3         static void Main()
 4         {
 5             //獲取描述MyClass類型的Type對象
 6             Type t = typeof(MyClass);
 7             Console.WriteLine($"Analyzing methods in {t.Name}");
 8             //MethodInfo對象在System.Reflection命名空間下
 9             //不獲取繼承方法,為實例方法,·為公用的
10             MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly|BindingFlags.Instance|BindingFlags.Public);
11             foreach (var methodInfo in mi)
12             {
13                 //返回方法的返回類型
14                 Console.Write(methodInfo.ReturnType.Name);
15                 //返回方法的名稱
16                 Console.Write($" {methodInfo.Name} (");
17                 //獲取方法闡述列表並保存在ParameterInfo對象組中
18                 ParameterInfo[] pi = methodInfo.GetParameters();
19                 for (int i = 0; i < pi.Length; i++)
20                 {
21                     //方法的參數類型名稱
22                     Console.Write(pi[i].ParameterType.Name);
23                     //方法的參數名
24                     Console.Write($" {pi[i].Name}");
25                     if (i+1<pi.Length)
26                     {
27                         Console.Write(", ");
28                     }
29                 }
30 
31                 Console.Write(")");
32                 Console.Write("\r\n");
33                 Console.WriteLine("--------------------------");
34             }
35             Console.ReadKey();
36         }
37     }
38 
39     class MyClass
40     {
41         private int x;
42         private int y;
43 
44         public MyClass()
45         {
46             x = 1;
47             y = 1;
48         }
49 
50         public int Sum()
51         {
52             return x + y;
53         }
54 
55         public bool IsBetween(int i)
56         {
57             if (x < i && i < y)
58             {
59                 return true;
60             }
61 
62             return false;
63         }
64 
65         public void Set(int a, int b)
66         {
67             x = a;
68             y = b;
69         }
70 
71         public void Set(double a, double b)
72         {
73             x = (int)a;
74             y = (int)b;
75         }
76 
77         public void Show()
78         {
79             System.Console.WriteLine($"x:{x},y:{y}");
80         }
81     }

輸出結果:

 

上面例子可以看出,只顯示了MyClass類顯示定義的方法,private int Sum() 也不顯示了 

六、使用反射調用方法

上面我們通過反射獲取到了類中的所有信息,下麵我們就再使用反射調用反射獲取到的方法。要調用反射獲取到的方法,則需要在MethodInfo實例上調用Invoke()方法,Invoke()的使用,在下麵例子中演示說明:

下麵例子是先通過反射獲取到要調用的方法,然後使用Invoke()方法,調用獲取到的指定方法:

 

 1     class Program
 2     {
 3         static void Main()
 4         {
 5             //獲取描述MyClass類型的Type對象
 6             Type t = typeof(MyClass);
 7             MyClass reflectObj = new MyClass();
 8             reflectObj.Show();
 9             //不獲取繼承方法,為實例方法,·為公用的
10             MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
11             foreach (var methodInfo in mi)
12             {
13 
14                 //獲取方法闡述列表並保存在ParameterInfo對象組中
15                 ParameterInfo[] pi = methodInfo.GetParameters();
16                 if (methodInfo.Name.Equals("Set", StringComparison.Ordinal) && pi[0].ParameterType == typeof(int))
17                 {
18                     object[] args = new object[2];
19                     args[0] = 9;
20                     args[1] = 10;
21                     methodInfo.Invoke(reflectObj,args);
22                 }
23             }
24             Console.ReadKey();
25         }
26     }
27 
28     class MyClass
29     {
30         private int x;
31         private int y;
32 
33         public MyClass()
34         {
35             x = 1;
36             y = 1;
37         }
38 
39         public int Sum()
40         {
41             return x + y;
42         }
43 
44         public bool IsBetween(int i)
45         {
46             if (x < i && i < y)
47             {
48                 return true;
49             }
50 
51             return false;
52         }
53 
54         public void Set(int a, int b)
55         {
56             x = a;
57             y = b;
58             Show();
59         }
60 
61         private void Set(double a, double b)
62         {
63             x = (int)a;
64             y = (int)b;
65         }
66 
67         public void Show()
68         {
69             System.Console.WriteLine($"x:{x},y:{y}");
70         }
71     }

獲取Type對象的構造函數

這個之前的闡述中,由於MyClass類型的對象都是顯示創建的,因此使用反射技術調用MyClass類中的方法是沒有任何優勢的,還不如以普通方式調用方便簡單呢,但是,如果對象是在運行時動態創建的,反射功能的優勢就會顯現出來。在這種情況下,要先獲取一個構造函數列表,然後調用列表中的某個構造函數,創建一個該類型的實例,通過這種機制,可以在運行時實例化任意類型的對象,而不必在聲明語句中指定類型。

示例代碼如下:

 

 1     class Program
 2     {
 3         static void Main()
 4         {
 5             //獲取描述MyClass類型的Type對象
 6             Type t = typeof(MyClass);
 7             int val;
 8             //使用這個方法獲取構造函數列表
 9             ConstructorInfo[] ci = t.GetConstructors();
10             int x;
11             for (x = 0; x < ci.Length; x++)
12             {
13                 //獲取當構造參數列表
14                 ParameterInfo[] pi = ci[x].GetParameters();
15                 if (pi.Length == 2)
16                 {
17                     //如果當前構造函數有2個參數,則跳出迴圈
18                     break;
19                 }
20             }
21 
22             if (x == ci.Length)
23             {
24                 return;
25             }
26             object[] consArgs = new object[2];
27             consArgs[0] = 10;
28             consArgs[1] = 20;
29             //實例化一個這個構造函數有連個參數的類型對象,如果參數為空,則為null
30 
31             object reflectOb = ci[x].Invoke(consArgs);
32 
33             MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
34             foreach (var methodInfo in mi)
35             {
36                 if (methodInfo.Name.Equals("Sum", StringComparison.Ordinal))
37                 {
38                     val = (int)methodInfo.Invoke(reflectOb, null);
39                     Console.WriteLine($"Sum is {val}");
40                 }
41             }
42             Console.ReadKey();
43         }
44     }
45 
46     class MyClass
47     {
48         private int x;
49         private int y;
50 
51         public MyClass(int
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 函數1 函數2 函數3 —————————————————————————————————————————————————————————————————— 1調用2,將變數a的地址做實參,傳給2的指針變數b。形如&a —》 *b。 2調用3,若仍以&b —》*c,則在指針變數c中,存入的是b的地址 ...
  • A いっしょ / Be Together (結論/暴力) "題目鏈接" 題目大意: 有 $n$ 個數字,要將它們變成相等,對每一個數字最多操作一次,如將 $a \to b$ 的代價為 $(a b)^2$ ,求出最小的代價。 大致思路: 根據不等式的知識可以知道,假設最後數字變為 $x$,那麼 $x$ ...
  • 恢復內容開始 每次講爬蟲的時候都會從“發送請求” 開始講,講到解析頁面的時候可能大部分讀者都會卡住,因為這部分確實需要一點XPATH或者CSS選擇器的前置知識。那麼有沒有不需要這麼複雜的操作就能把頁面信息讀取出來的方法呢? 答案是:有。 Lassie是一個超簡單的頁面信息檢索工具,它能夠通過幾行代碼 ...
  • 明天又雙叒叕是一年一度的七夕戀愛節了! 又是一波絕好的機會!恩愛秀起來! 購物車清空!禮物送起來!朋友圈曬起來! 等等! 什麼?! 你還沒準備好七夕禮物麽? 但其實你不知道要送啥? 原來又雙叒叕要到了 全民不知道送什麼禮物的系列日子了… 需要Python軟體工具和學習資料的小伙伴可以點擊下方鏈接自行 ...
  • 下載安裝 從 "https://golang.org/dl/" 下載最新的安裝包。 windows直接執行exe,按指示進行安裝(預設安裝目錄是:C:\Go) linxu解壓後複製到你喜歡目錄就行(一般放在:/usr/local/go) 配置環境變數 首先,應在全局環境變數里增加: ,指定 的安裝位 ...
  • Springcloud Aibaba現在這麼火,我一直想寫個基於Springcloud Alibaba一步一步構建微服務架構的系列博客,終於下定決心從今天開始本系列文章的第一篇 基礎環境準備。 該系列文章內容主要基於三個微服務:用戶服務 ,訂單服務 ,產品服務 用到的組件有: 註冊中心、配置中心 N ...
  • 關鍵詞:工作流定時任務 流程引擎定時任務設置 工作流系統定時任務配置 開源工作流引擎 開源工作流系統 一、定時任務的作用 發送郵件,發送簡訊。 處理節點自動執行的任務。比如:一個節點的待辦工作是機器執行的,需要等到滿足一定的條件才能運行下去,我們稱為自動執行任務節點,這個任務叫自動執行任務。 啟動定... ...
  • 這是Orleans團隊的帖子。Orleans是用於使用.NET構建分散式應用程式的跨平臺框架。有關更多信息,請參見 "https://github.com/dotnet/orleans" 。 我們很高興宣佈Orleans 3.0版本。自Orleans 2.0以來,進行了大量改進和修複,並提供了一些新 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...