【微信平台,此文僅授權《NCC 開源社區》訂閱號發布】
本篇主要研究類型、類型成員的各種信息和標識,通過反射的操作將信息解析出來。
本文主目的的通過反射操作,生成輸出類似下圖的信息。
在此之前記一下:
C# 中的訪問修飾符:public、private、protected、internal、protected internal。
C# 兩個成員關鍵字 readonly、const。
C# 聲明修飾符: sealed、static、virtual、new 、abstract、override。
我們根據反射的類型對象,大概分為:類、值類型、數組、結構體、枚舉、接口、抽象類、委托、事件、各種泛型(泛型類、泛型方法、泛型構造函數等)。
此文花了我一天半時間,除了寫文章外,查看大量文檔資料,創建了很多項目,進行了大量測試驗證,最終整理出來。
至此此系列已經進行到第九篇啦。
1,判斷類型
從 Type 中解析類型信息,筆者使用思維導圖整理如圖
判斷是否某種類型
一般來說,如果有兩個 Type 對象,要判斷兩個 Type 所反射的類型,是否為同一種類型,可以使用 ==
。
Type A = typeof(ClassA);
Type B = typeof(ClassB);
Console.WriteLine(A == B);
1.1 類和委托
1.1.1 判斷是否類型或委托
Type.IsClass
屬性可以判斷一個類型是否為類或者委托。符合條件的會有普通的類(包括泛型)、抽象類(abstract class)、委托(delegate)。
它可以排除值類型和接口。例如簡單值類型、結構體、枚舉、接口。
1.1.2 判斷是否泛型
Type.IsGenericType
屬性可以判斷類或委托是否為泛型類型。
Type.IsGenericTypeDefinition
屬性可以判斷 Type 是否是未綁定參數類型的泛型類型。
Type.IsConstructedGenericType
屬性判斷是否可以此 Type 創建泛型實例。
如果是已綁定參數類型的泛型,則可以使用 Activator.CreateInstance()
等方式實例化類型。
實驗過程:
創建三個類型
public delegate void DeA();
public delegate void DeB<T>(T t);
public class ClassC<T>
{
public ClassC(T t) { }
}
打印輸出
// 普通委托
Type typeA = typeof(DeA);
Console.WriteLine("類型名稱:" + typeA.Name);
Console.WriteLine("是否為類或委托:" + typeA.IsClass);
Console.WriteLine("是否為泛型:" + typeA.IsGenericType);
Console.WriteLine("是否已綁定參數類型:" + typeA.IsGenericTypeDefinition);
Console.WriteLine("可以用此 Type 創建實例:" + typeA.IsConstructedGenericType);
// 泛型委托,不綁定參數類型
Type typeB = typeof(DeB<>);
Console.WriteLine("\n\n類型名稱:" + typeB.Name);
Console.WriteLine("是否為類或委托:" + typeB.IsClass);
Console.WriteLine("是否為泛型:" + typeB.IsGenericType);
Console.WriteLine("是否已綁定參數類型:" + typeB.IsGenericTypeDefinition);
Console.WriteLine("可以用此 Type 創建實例:" + typeB.IsConstructedGenericType);
// 泛型委托,綁定參數類型
Type typeBB = typeof(DeB<int>);
Console.WriteLine("\n\n類型名稱:" + typeBB.Name);
Console.WriteLine("是否為類或委托:" + typeBB.IsClass);
Console.WriteLine("是否為泛型:" + typeBB.IsGenericType);
Console.WriteLine("是否已綁定參數類型:" + typeBB.IsGenericTypeDefinition);
Console.WriteLine("可以用此 Type 創建實例:" + typeBB.IsConstructedGenericType);
// 泛型類,未綁定參數
Type typeC = typeof(ClassC<>);
Console.WriteLine("\n\n類型名稱:" + typeC.Name);
Console.WriteLine("是否為類或委托:" + typeC.IsClass);
Console.WriteLine("是否為泛型:" + typeC.IsGenericType);
Console.WriteLine("是否已綁定參數類型:" + typeC.IsGenericTypeDefinition);
Console.WriteLine("可以用此 Type 創建實例:" + typeC.IsConstructedGenericType);
// 泛型類型,已綁定參數
Type typeD = typeof(ClassC<int>);
Console.WriteLine("\n\n類型名稱:" + typeD.Name);
Console.WriteLine("是否為類或委托:" + typeD.IsClass);
Console.WriteLine("是否為泛型:" + typeD.IsGenericType);
Console.WriteLine("是否已綁定參數類型:" + typeD.IsGenericTypeDefinition);
Console.WriteLine("可以用此 Type 創建實例:" + typeD.IsConstructedGenericType);
1.1.3 泛型的參數名稱和泛型限定
獲取泛型類型定義時,泛型參數的名稱
public class MyClass<T1,T2,T3,T4,T5> { }
Type type = typeof(MyClass<,,,,>);
var types = ((System.Reflection.TypeInfo)type).GenericTypeParameters;
foreach (var item in types)
{
Console.WriteLine(item.Name);
}
輸出
T1
T2
T3
T4
T5
TypeInfo 用於處理各類類型的泛型類型聲明。
《C#反射與特性(四):實例化類型》第三節中,我們探究了泛型的各種實例化方式。
泛型約束
對於類和方法來說,使用泛型版本,可能會進行泛型約束,我們需要將約束解析出來。
Type 中, GetGenericParameterConstraints
和 GenericParameterAttributes
屬性,可以判斷約束類型。
約束 | 描述 |
---|---|
where T : struct |
值類型 |
where T : class |
類型參數必須是引用類型。 此約束還應用於任何類、接口、委托或數組類型 |
where T : notnull |
類型參數必須是不可為 null 的類型 |
where T : unmanaged |
類型參數必須是不可為 null 的非托管類型,跟struct十分相似,但是unmanaged是不安全的。 |
where T : new() |
類型參數必須具有公共無參數構造函數。 與其他約束一起使用時,new() 約束必須最后指定。 new() 約束不能與 struct 和 unmanaged 約束結合使用。 |
where T : <基類名> |
類型參數必須是指定的基類或派生自指定的基類 |
where T : <接口名稱> |
類型參數必須是指定的接口或實現指定的接口。 可指定多個接口約束。 約束接口也可以是泛型。 |
where T : U |
為 T 提供的類型參數必須是為 U 提供的參數或派生自為 U 提供的參數 |
GetGenericParameterConstraints
可以獲取到參數類型,不過只能對 struct、class、<基類名>、<接口名稱>、T : U
有效。
- 這些約束之間,有着復雜的沖突關系,這里就不一一列舉了。
- 而有些類型本身就代表包含多種約束,例如 struct 本身包含
new()
、notnull
。 - 有些約束條件是可以互相組合的。
從上面看來,要解析泛型約束,不是容易的事。
GenericParameterAttributes 枚舉
但是我們來划分一下,針對不同情況下的組合,來理清一下 Type 和 GenericParameterAttributes 的關系。
先看一下 GenericParameterAttributes
枚舉,此枚舉是用來描述泛型類或方法上泛型參數約束的。
public enum GenericParameterAttributes
{
None = 0, // 無特殊情況
Covariant = 1, // 泛型類型參數是可協變的
Contravariant = 2, // 泛型類型參數是逆變的
VarianceMask = 3, // Contravariant 和 Covariant 的集合
ReferenceTypeConstraint = 4, // 引用類型
NotNullableValueTypeConstraint = 8, // 是值類型且不為空
DefaultConstructorConstraint = 16, // 無參數構造函數
SpecialConstraintMask = 28 // 所有特殊標記的集合
}
接下來看看不同約束條件和對應的 GenericParameterAttributes
枚舉值。
泛型約束關系
泛型約束有各種沖突關系和約束特性,我們來通過表格和圖片,一一列舉出來。
約束 | Type | 枚舉值 | 沖突 | 必須放在開頭 |
---|---|---|---|---|
struct | 值類型 | 8,16 | 只能單獨使用 | 是 |
class | 4 | struct,notnull,unmanaged,<基類名> | 是 | |
notnull | 0 | struct,class,unmanaged | 是 | |
unmanaged | struct | 8,16 | 只能單獨使用 | 是 |
new() | 16 | struct,unmanaged | 必須放在最后 | |
<基類名> | <基類名> | 0 | struct,notnull,unmanaged | 是 |
<接口名> | <接口名> | 0 | struct,unmanaged | 否 |
T : U | U | 0 | struct | 否 |
注:T : U
使用時雖然不提示與其它約束沖突,如果繼承的約束有沖突,可能會在編譯時或運行期可能會報錯。
<接口名> 編寫代碼時,不與其它約束沖突,但是實際上有些是不能同時使用的。
unmanaged, BaseInterFace
可以用作約束條件,但是 unmanaged 應該是非托管類型,這里我們就不考慮了。
泛型約束關系如圖所示:
看完圖片后是不是感覺思路很清晰了呢~
泛型約束比較多,他們有多種組合,但是從上圖,可以判斷組合時:
①(紅)
②(黃)(N個藍)
③(黃)(N個藍)(橙)
④(任意一種顏色)
⑤(N個藍色)
由於代碼比較多,這里就不顯示了,代碼已經上傳至碼雲 解析泛型
關於泛型的反射,可以參考這里 https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generics-and-reflection
1.1.4 是否委托
經過前面的操作,已經可以篩選出一個類型是否為類型或委托,那么判斷一個類型是否為委托,可以使用 IsSubclassOf()
,可以判斷一個 Type 是否為委托類型。
IsSubclassOf()
可以判斷當前 Type 是否派生於 參數中的 Type。
type.IsSubclassOf(typeof(Delegate));
另外,有個多播委托 MulticastDelegate
,可以使用
type.IsSubclassOf(typeof(MulticastDelegate))
1.1.5 訪問修飾符
在命名空間中說明的類和委托只能使用 public、internal 兩個修飾符修飾訪問權限。如果不指定的話,默認下是 internal 。
Type 的兩個屬性 IsPublic
、IsNotPublic
可以對此進行識別。
測試:
public class A { }
internal class B { }
class C { }
Main 中輸出
Type typeA = typeof(A);
Type typeB = typeof(B);
Type typeC = typeof(C);
Console.WriteLine(typeA.Name);
Console.WriteLine("是否public: "+typeA.IsPublic);
Console.WriteLine("是否protected: " + typeA.IsNotPublic);
Console.WriteLine("\n"+typeB.Name);
Console.WriteLine("是否public: " + typeB.IsPublic);
Console.WriteLine("是否protected: " + typeB.IsNotPublic);
Console.WriteLine("\n" + typeC.Name);
Console.WriteLine("是否public: " + typeC.IsPublic);
Console.WriteLine("是否protected: " + typeC.IsNotPublic);
輸出結果
A
是否public: True
是否protected: False
B
是否public: False
是否protected: True
C
是否public: False
是否protected: True
1.1.6 密封類、靜態類型、抽象類
密封類是不能被繼承的類型,通過 Type 的 IsSealed
可以判斷。
public sealed class A { }
Console.WriteLine(typeof(A).IsSealed);
sealed 也可以修飾委托。
判斷是否為抽象類
public abstract class MyClass { }
Console.WriteLine(typeof(MyClass).IsAbstract);
定義類時,static、abstract、sealed 任意兩個不能在一起調用。
如果一個類是靜態類,那么 IsSealed
和 IsAbstract
都是 true。
Type 中沒有判斷類是否為靜態類的屬性或方法,但是可以通過上面的方法判斷是否為靜態類。
我們可以做一下實驗
public sealed class A { }
public abstract class B { }
public static class C { }
Type typeA = typeof(A);
Type typeB = typeof(B);
Type typeC = typeof(C);
Console.WriteLine("密封類:");
Console.WriteLine("IsSealed:" + typeA.IsSealed);
Console.WriteLine("IsAbstract:" + typeA.IsAbstract);
Console.WriteLine("\n抽象類類:");
Console.WriteLine("IsSealed:" + typeB.IsSealed);
Console.WriteLine("IsAbstract:" + typeB.IsAbstract);
Console.WriteLine("\n靜態類");
Console.WriteLine("IsSealed:" + typeC.IsSealed);
Console.WriteLine("IsAbstract:" + typeC.IsAbstract);
輸出結果
密封類:
IsSealed:True
IsAbstract:False
抽象類類:
IsSealed:False
IsAbstract:True
靜態類
IsSealed:True
IsAbstract:True
1.1.7 嵌套類訪問權限
下面是有關於嵌套類型的 Type 的 屬性。 類和委托都可以使用。
屬性 | 說明 |
---|---|
IsNested | 獲取一個指示當前 Type 對象是否表示其定義嵌套在另一個類型的定義之內的類型的值。 |
IsNestedAssembly | 獲取一個值,通過該值指示 Type 是否是嵌套的並且只能在它自己的程序集內可見。 |
IsNestedFamANDAssem | 獲取一個值,通過該值指示 Type 是否是嵌套的並且只對同時屬於自己家族和自己程序集的類可見。 |
IsNestedFamily | 獲取一個值,通過該值指示 Type 是否是嵌套的並且只能在它自己的家族內可見。 |
IsNestedFamORAssem | 獲取一個值,通過該值指示 Type 是否是嵌套的並且只對屬於它自己的家族或屬於它自己的程序集的類可見。 |
IsNestedPrivate | 獲取一個值,通過該值指示 Type 是否是嵌套的並聲明為私有。 |
IsNestedPublic | 獲取一個值,通過該值指示類是否是嵌套的並且聲明為公共的。 |
1.1.8 特性
索特性的方式有兩種
- 調用 Type 或者 MemberInfo 的 GetCustomAttributes 方法;
- 調用 Attribute.GetCustomAttribute 或者 Attribute.GetCustomAttributes 方法;
《C#反射與特性(七):自定義特性以及應用》中,對特性的使用做了很詳細的介紹,這里不再贅述。
1.1.9 父類、接口
屬性 | 說明 |
---|---|
BaseType | 獲取當前 Type直接從中繼承的類型。 |
方法 | 說明 |
---|---|
GetInterface(String) | 搜索具有指定名稱的接口。 |
GetInterfaces() | 當在派生類中重寫時,獲取由當前 Type實現或繼承的所有接口。 |
Type type = typeof(List<>);
Console.WriteLine("List<> 的父類為:" + type.BaseType);
Console.WriteLine("List<> 繼承的接口:");
Type[] types = type.GetInterfaces();
foreach (var item in types)
{
Console.WriteLine(item.Name);
}
1.2 值類型
Type.IsValueType
可以判斷一個 Type 是否為值類型,簡單值類型、結構體、枚舉,都符合要求。
Type.IsEnum
判斷 Type 是否為枚舉。
Type.IsPrimitive
判斷 Type 是否為基礎類型。
通過以下過程可以判斷一個類型屬性何種值類型
public enum MyTest
{
None = 0, // 不是值類型
Enum = 1, // 枚舉
Struct = 2, // 結構體
Base = 3 // 基礎類型
}
public static MyTest Test(Type type)
{
if (!type.IsValueType)
return MyTest.None;
if (type.IsEnum)
return MyTest.Enum;
return type.IsPrimitive ? MyTest.Base : MyTest.Struct;
}
枚舉 Type,有如下方法幫助獲取枚舉信息:
方法 | 說明 |
---|---|
GetElementType() | 當在派生類中重寫時,返回當前數組、指針或引用類型包含的或引用的對象的 Type。 |
GetEnumName(Object) | 返回當前枚舉類型中具有指定值的常數的名稱。 |
GetEnumNames() | 返回當前枚舉類型中各個成員的名稱。 |
GetEnumUnderlyingType() | 返回當前枚舉類型的基礎類型。 |
GetEnumValues() | 返回當前枚舉類型中各個常數的值組成的數組。 |
1.3 接口
Type.IsInterface
屬性,判斷 Type 是否為接口。
1.4 數組
IsArray
判斷是否為數組,GetArrayRank()
獲取數組的維數。
通過 GetElementType
可以獲取數組的元素類型
IsSZArray
判斷是否為交錯數組/鋸齒數組,IsVariableBoundArray
判斷是否為一維或多維數組。
IsSZArray
和 IsVariableBoundArray
是 .NET Core 2.0 以上、.NET Standard 2.1 以上才有的。
Type a = typeof(int[,,,,]);
Console.WriteLine(a.Name);
Console.WriteLine("數組元素類型:" + a.GetElementType());
Console.WriteLine("是否為數組:" + a.IsArray);
Console.WriteLine("交錯數組:" + a.IsSZArray);
Console.WriteLine("一維或多維數組" + a.IsVariableBoundArray);
Console.WriteLine("數組維數:" + a.GetArrayRank());
Console.WriteLine("\n\n");
Type b = typeof(int[][][][]);
Console.WriteLine(b.Name);
Console.WriteLine("數組元素類型:" + b.GetElementType());
Console.WriteLine("是否為數組:" + b.IsArray);
Console.WriteLine("交錯數組:" + b.IsSZArray);
Console.WriteLine("一維或多維數組" + b.IsVariableBoundArray);
Console.WriteLine("數組維數:" + b.GetArrayRank());
不過 GetElementType()
不能一次性拿到最初的元素類型,GetArrayRank
對交錯數組也無效。
下面的方法可以快速解析值類型的交錯數組。
// 只能解析值類型、系統基礎類型,例如 int 等
public static (Type, int) Test(Type type)
{
if (!type.IsSZArray)
return (type, 0);
int num = 0;
Type that = type;
while (true)
{
that = that.GetElementType();
num += 1;
if (that.IsPrimitive)
break;
}
return (that, num);
}
調用
Type b = typeof(int[][][][]);
var result = Test(b);
Console.WriteLine("元素類型:" + result.Item1);
Console.WriteLine("鋸齒數:" + result.Item2);
復雜類型的交錯數組,可以使用字符串處理。
2, 類型成員
通過第一章的操作,已經可以解析程序集的大綱圖了,現在開始來獲取類型內部的細節,構建更為清晰的信息。
解析類型結構,過程大致如下
2.1 類
一個類由以下一個或多個成員組成:
成員類型 | 說明 |
---|---|
PropertyInfo | 類型的屬性信息 |
FieldInfo | 類型的字段信息 |
ConstructorInfo | 類型的構造函數信息 |
MethodInfo | 類型的方法 |
ParameterInfo | 構造函數或方法的參數 |
EventInfo | 類型的事件 |
特性的話,在《C#反射與特性(七):自定義特性以及應用》已經講解了,這里不再贅述。
2.1.1 訪問修飾符
public、private兩個修飾符,判斷起來十分簡單;
C# 關鍵字 protected
和 internal
在 IL 中沒有任何意義,且不會用於反射 API 中。也就是說在反射中看來,這兩個訪問修飾符沒作用;不過對於獲取信息來說,還是需要想辦法解析。
protected、internal、protected internal 對於反射調用來說,是沒有意義的,不過對於獲取信息來說,還是需要想辦法解析。
判斷是否為 internal
可以使用 IsAssembly
;判斷是否為 protected internal
,可以使用IsFamilyOrAssembly
;兩個屬性一起用,結果都是 false
的話,則是 protected
。
屬性 | 說明 |
---|---|
IsAssembly | 是否為 internal |
IsFamily | 是否為 protected |
IsFamilyOrAssembly | 判斷是否為 protected internal |
注: protected internal
、internal protected
是一樣的。
下面方法可以判斷並且返回訪問修飾符名稱
public static string Visibility(FieldInfo field)
{
return
field.IsPublic ? "public" :
field.IsPrivate ? "private" :
field.IsAssembly ? "internal" :
field.IsFamily ? "protected" :
field.IsFamilyOrAssembly ? "protected internal" :
null;
}
2.1.2 其它修飾符
readonly、static、const 三個修飾符,const 不能與其它修飾符同時存在。
屬性 | 說明 |
---|---|
IsLiteral | 獲取一個值,通過該值指示該值是否在編譯時寫入並且不能更改 |
IsStatic | static 修飾的字段,注意 const 也屬於 static。 |
IsInitOnly | 獲取一個值,通過該值指示此字段是否只能在構造函數的主體中設置 |
下面的方法可以判斷、返回相應的修飾符
public static string Only(FieldInfo field)
{
if (field.IsLiteral)
return "const";
if (field.IsStatic && field.IsInitOnly)
return "readonly static";
if (field.IsStatic)
return "static";
if (field.IsInitOnly)
return "readonly";
return string.Empty;
}
const int a;
使用 IsStatic
結果為 true
,因為 const 也屬於 static。
2.1.3 字段
通過 2.1.1 和 2.1.2 ,可以解析字段的信息了。
下面來測試一下。
定義一個類型
public class MyClass
{
public int a;
internal int b;
protected int c;
protected internal int d;
private int e;
public readonly static float f = 1;
}
輸出解析數據
Type type = typeof(MyClass);
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Static | BindingFlags.Instance);
IEnumerable<FieldInfo> fields1 = type.GetRuntimeFields();
foreach (var item in fields)
{
StringBuilder builder = new StringBuilder();
builder.Append(GetVisibility(item) + " ");
builder.Append(GetRead(item) + " ");
builder.Append(item.FieldType.Name + " ");
builder.Append(item.Name + " ;");
Console.WriteLine(builder.ToString());
}
因為反射的顯示信息的話,主要是顯示元數據,而且 {get;set;}
屬性會自動生成私有字段,所以上面的代碼會將這些也顯示出來。將獲取條件改成 BindingFlags.Public | BindingFlags.GetField | BindingFlags.Static | BindingFlags.Instance
。
2.1.4 方法、參數
排除屬性的方法
當我們編寫一個屬性,編譯時,編譯器會生成對應的 get 和 set 方法,我們一般來說,只是需要顯示程序員編寫的方法,而非系統生成的。
系統生成的屬性的方法,會帶有一個 System.Runtime.CompilerServices.CompilerGeneratedAttribute
特性,通過此特性可以排除系統生成的方法。
public static bool IsPropertyOfAttr(MethodInfo method)
{
return method.GetCustomAttributes().Any(x => x.GetType() == typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute));
}
方法的訪問修飾符
判斷方法訪問修飾符的代碼如下
public static string GetVisibility(MethodInfo method)
{
return
method.IsPublic ? "public" :
method.IsPrivate ? "private" :
method.IsAssembly ? "internal" :
method.IsFamily ? "protected" :
method.IsFamilyOrAssembly ? "protected internal" :
null;
}
前面已經進行了相應的講解,這里不在贅述。
重寫與隱藏關鍵字
方法,可以有以下關鍵字修飾:virtual、override、abstract、new;
從繼承關系上來說,分類上,一個方法可能是 virtual、abstract;然后繼承后,重寫 virtual 修飾的方法可能是 override 、 new;abstract 修飾的方法只能使用 override 。
以下屬性可以區分修飾符:
IsAbstract
、IsVirtual
、IsHideBySig
,IsFinal
。
virtual、override、abstract、new 修飾的方法,IsHideBySig
結果都是 true,可用此屬性判斷方法是否有抽象、重寫等關鍵字修飾。
對於 virtual、override 修飾的方法,IsVirtual
為 true,new 修飾的方法 IsVirtual
為 flase。
但是一個方法,如果是實現了接口方法的話,使用 IsVirtual
也會返回 true
,IsHideBySig
也會返回 true。
那么就剩下區分 virtual
、 override
了,如果當前方法是重寫了父類的,使用MethodInfo.GetBaseDefinition()
可以返回當前方法的所重寫父類的方法;如果沒有重寫,那么就返回方法本身。
IsVirtual
可以判斷當前方法是否可以被重寫。
但是但是,一個在當前類中定義的,類似 public string Test(){}
的方法,可以被重寫,很容易被判斷為 new。需要在最后做個判斷。
當獲取到一個 MethodInfo 時,要區分上面的修飾符,可以使用以下代碼流程。
// virtual override abstract new
public static string IsOver(Type type,MethodInfo method)
{
// 沒有相應的信息,說明沒有使用以上關鍵字修飾
if (!method.IsHideBySig)
return string.Empty;
// 是否抽象方法
if (method.IsAbstract)
return "abstract";
// virtual、override、實現接口的方法
if (method.IsVirtual)
{
// 實現接口的方法
if (method.IsFinal)
return string.Empty;
// 沒有被重寫,則為 virtual
if (method.Equals(method.GetBaseDefinition()))
return "virtual";
else
return "override";
}
// new
else
{
// 如果是當前類型中定義的方法,則只是一個普通的方法
if (type == method.DeclaringType)
return string.Empty;
return "new";
}
}
獲取返回類型
可以從 ReturnParameter
、ReturnType
和 ReturnTypeCustomAttributes
獲取有關返回類型的信息。
ReturnTypeCustomAttributes
是獲取特性信息的,這里先不處理。
// 獲取返回類型
public static string GetReturn(MethodInfo method)
{
Type returnType = method.ReturnType;
ParameterInfo returnParam = method.ReturnParameter;
if (returnType == typeof(void))
return "void";
if (returnType.IsValueType)
{
// 判斷是否 (type1,type2) 這樣的返回
if (returnParam.ParameterType.IsGenericType)
{
Type[] types = returnParam.ParameterType.GetGenericArguments();
string str = "(";
for (int i = 0; i < types.Length; i++)
{
str += types[i].Name;
if (i < types.Length - 1)
str += ",";
}
str += ")";
return str;
}
return returnType.Name;
}
// 這里暫不處理復雜的返回類型,例如數組,泛型等。
return returnType.Name;
}
method.ReturnType
和 method.ReturnParameter.ParameterType
是一樣的。
一般使用 ReturnType
就行了,有些特殊的語法要使用 ReturnParameter
。
筆者暫時沒有碰到有區分的使用場景。
是否異步方法
使用以下代碼判斷是否異步方法
public static string GetAsync(MethodInfo info)
{
return info.GetCustomAttribute(typeof(AsyncStateMachineAttribute))==null?"":"async ";
}
泛型方法
通過以下代碼可以判斷是否為泛型方法,並且返回名稱。
// 判斷方法是否為泛型方法,並且返回泛型名稱
public static string GetMethodName(MethodInfo method)
{
if (!method.IsGenericMethod)
return method.Name;
Type[] types = method.GetGenericArguments();
string str = method.Name + "<";
for (int i = 0; i < types.Length; i++)
{
str += types[i].Name;
if (i < types.Length - 1)
str += ",";
}
str += ">";
return str;
}
方法參數
步驟一:判斷參數是否有 in、ref、out 修飾,如果是的話,類型名稱后面會帶有字符 &
;params 的話,會帶有一個 ParamArrayAttribute
特性。
步驟二:獲取參數類型;如果是 in、ref、out 修飾的話,類型名稱后面會帶有一個 &
,需要去除;
步驟三:是否具有默認值,如果存在默認值的話,就返回默認值。
// 解析方法的參數
public static string GetParams(MethodInfo method)
{
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 0)
return string.Empty;
int length = parameters.Length - 1;
StringBuilder str = new StringBuilder();
for (int i = 0; i <= length; i++)
{
str.Append(InRefOut(parameters[i]) + " ");
// 這里不對復雜類型等做處理
str.Append(GetParamType(parameters[i]) + " ");
str.Append(parameters[i].Name);
str.Append(HasValue(parameters[i]) + " ");
if (i < length)
str.Append(",");
}
return str.ToString();
}
public static string InRefOut(ParameterInfo parameter)
{
// in、ref、out ,類型后面會帶有 & 符號
if (parameter.ParameterType.Name.EndsWith("&"))
{
return
parameter.IsIn ? "in" :
parameter.IsOut ? "out" :
"ref";
}
if (parameter.GetCustomAttributes().Any(x => x.GetType() == typeof(ParamArrayAttribute)))
return "params";
return string.Empty;
}
// 獲取類型
public static string GetParamType(ParameterInfo parameter)
{
string typeName = parameter.ParameterType.Name;
if (typeName.EndsWith("&"))
typeName = typeName.Substring(0, typeName.Length - 1);
return typeName;
}
// 是否為可選參數,是否有默認值
public static string HasValue(ParameterInfo parameter)
{
if (!parameter.IsOptional)
return string.Empty;
object value = parameter.DefaultValue;
return " = " + value.ToString();
}
學以致用
學習如何獲取、解析方法的信息后,我們可以在這里實踐一下。
定義以下類型,我們最終需要的是 MyClass。
interface A
{
void TestA();
}
public abstract class B
{
public abstract void TestB();
}
public abstract class C : B
{
public virtual void TestC()
{
}
public virtual void TestD()
{
}
}
public class MyClass : C, A
{
public void TestA()
{
throw new NotImplementedException();
}
public override void TestB()
{
throw new NotImplementedException();
}
public override void TestC()
{
base.TestC();
}
new public void TestD()
{
}
public (bool, bool) TestE()
{
return (true, true);
}
public string TestF<T>(T t)
{
return t.GetType().Name;
}
public string TestG(in string a, ref string aa, out string b, string c = "666")
{
b = "666";
return string.Empty;
}
public string TestH(params string[] d)
{
return string.Empty;
}
}
將 2.1.4 出現的解析方法,復制粘貼到項目中,使用以下代碼即可解析出一個類中的方法。
Type type = typeof(MyClass);
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Instance);
foreach (MethodInfo item in methods)
{
StringBuilder builder = new StringBuilder();
builder.Append(GetVisibility(item) + " ");
builder.Append(item.GetGetMethod(true).IsStatic ? "static " : string.Empty);
builder.Append(IsOver(type, item) + " ");
builder.Append(GetReturn(item) + " ");
builder.Append(GetMethodName(item) + " ");
builder.Append("(" + GetParams(item) + ")");
Console.WriteLine(builder.ToString());
}
這里不對泛型和數組等復雜類型進行解析,也不輸出特性。
可以嘗試將 MyClass 換成 List<> 等類型進行測試。
輸出效果:
public void TestA ()
public override void TestB ()
public override void TestC ()
public void TestD ()
public (Boolean,Boolean) TestE ()
public String TestF<T> ( T t )
public String TestG (in String a ,ref String aa ,out String b , String c = 666 )
public String TestH (params String[] d )
完整代碼已上傳到碼雲,點擊查看 解析方法與參數 。
2.1.5 構造函數
構造函數的話,沒有返回類型,也沒有重寫,獲取參數方法的部分,
因為有很多跟 2.1.4 重復的代碼,因此這里不再贅述,代碼已經上傳到碼雲,可以參考 解析構造函數 。
2.1.6 屬性
正常來說呢,這樣寫屬性是可以的,但是過多的修飾符對屬性來說是沒意義的。
public class MyClass
{
public int a { get; set; }
internal int b { get; set; }
protected int c { get; set; }
protected internal int d { get; set; }
private int e { get; set; }
public static float f { get; set; } = 1;
}
PropertyInfo
沒有像 FieldInfo
那么豐富的判斷修飾符的屬性。
但是呢,獲取到屬性的方法,則可以獲取訪問修飾符。
獲取訪問修飾符
跟獲取方法的訪問修飾符一樣,稍微調整以下即可。
public static string GetVisibility(PropertyInfo property)
{
MethodInfo method = property.GetGetMethod();
return
method.IsPublic ? "public" :
method.IsPrivate ? "private" :
method.IsAssembly ? "internal" :
method.IsFamily ? "protected" :
method.IsFamilyOrAssembly ? "protected internal" :
null;
}
獲取重寫關鍵字
// virtual override abstract new
public static string IsOver(Type type, PropertyInfo property)
{
MethodInfo method = property.GetGetMethod(true);
// 沒有相應的信息,說明沒有使用以上關鍵字修飾
if (!method.IsHideBySig)
return string.Empty;
// 是否抽象方法
if (method.IsAbstract)
return "abstract";
// virtual、override、實現接口的方法
if (method.IsVirtual)
{
// 實現接口的方法
if (method.IsFinal)
return string.Empty;
// 沒有被重寫,則為 virtual
if (method.Equals(method.GetBaseDefinition()))
return "virtual";
else
return "override";
}
// new
else
{
// 如果是當前類型中定義的方法,則只是一個普通的方法
if (type == method.DeclaringType)
return string.Empty;
return "new";
}
}
解析屬性構造器
// 解析屬性的構造器
public static string GetConstructor(PropertyInfo property)
{
string str = "{ ";
if (property.CanRead)
str += "get; ";
if (property.CanWrite)
str += "set; ";
str += "}";
return str;
}
反射是無法直接拿到屬性的默認值的,詳細請參考 https://www.ojit.com/article/3058539。
以上,測試代碼,可以到碼雲查看 解析屬性
2.1.7 事件
本節沿用 2.1.4 中解析方法的所有函數。
定義委托和事件如下
public delegate void DeTest();
public abstract class A
{
public abstract event DeTest TestA;
}
public abstract class B : A
{
public virtual event DeTest TestB;
public event DeTest TestC;
}
public class MyClass : B
{
public override event DeTest TestA;
public override event DeTest TestB;
new public event DeTest TestC;
}
解析事件過程
Type type = typeof(MyClass);
EventInfo[] events = type.GetEvents(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Instance);
foreach (var item in events)
{
MethodInfo method = item.GetAddMethod();
StringBuilder builder = new StringBuilder();
builder.Append(GetVisibility(method) + " ");
builder.Append(method.IsStatic ? "static " : string.Empty);
builder.Append(IsOver(type, method) + " ");
builder.Append("event ");
builder.Append(item.EventHandlerType.Name + " ");
builder.Append(item.Name + ";");
Console.WriteLine(builder.ToString());
}
解析過程是非常簡單的。
2.1.8 索引器
我們定義一個類型和索引器如下
public class MyClass
{
private string[] MyArray;
public MyClass()
{
MyArray = new string[] { "a", "b", "c", "d", "e" };
}
// 這里不處理 search
public string this[int index,string search]
{
get
{
return MyArray[index];
}
set
{
MyArray[index] = value;
}
}
}
索引器在編譯時,會生成屬性和方法,所以使用反射獲取屬性時,會把索引器生成的屬性包含在內。
構造器會自動生成一個 public string Item { get; set; }
的屬性。
本節使用 2.1.6 中解析屬性的代碼。
將屬性獲取方法優化如下,會區分輸出類型中的屬性和構造器。
Type type = typeof(MyClass);
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance);
foreach (PropertyInfo item in properties)
{
StringBuilder builder = new StringBuilder();
builder.Append(GetVisibility(item) + " ");
builder.Append(item.GetGetMethod(true).IsStatic ? "static " : string.Empty);
builder.Append(IsOver(type, item) + " ");
builder.Append(item.PropertyType + " ");
if (item.Name == "Item")
{
builder.Append("this[");
ParameterInfo[] paras = item.GetIndexParameters();
int length = paras.Length - 1;
for (int i = 0; i <= length; i++)
{
builder.Append(paras[i].ParameterType.Name + " " + paras[i].Name);
if (i < length)
builder.Append(",");
}
builder.Append("]");
}
else
{
builder.Append(item.Name + " ");
builder.Append(GetConstructor(item));
}
Console.WriteLine(builder.ToString());
}
2.1.9 獲取特性
類型、方法、屬性、字段等,都可以使用特性修飾,我們要通過反射獲取特性后,還要將特性結果還原出程序員寫代碼時設置的值。
代碼如下
/// <summary>
/// 解析輸出類型、方法、屬性、字段等特性
/// </summary>
/// <param name="attrs"></param>
/// <returns></returns>
public static string[] GetAttrs(IList<CustomAttributeData> attrs)
{
List<string> attrResult = new List<string>(); ;
foreach (var item in attrs)
{
Type attrType = item.GetType();
string str = "[";
str += item.AttributeType.Name;
// 構造函數中的值
IList<CustomAttributeTypedArgument> customs = item.ConstructorArguments;
// 屬性的值
IList<CustomAttributeNamedArgument> arguments = item.NamedArguments;
// 沒有任何值
if (customs.Count == 0 && arguments.Count == 0)
{
attrResult.Add(str + "]");
continue;
}
str += "(";
if (customs.Count != 0)
{
str += string.Join(",", customs.ToArray());
}
if (customs.Count != 0 && arguments.Count != 0)
str += ",";
if (arguments.Count != 0)
{
str += string.Join(",", arguments.ToArray());
}
str += ")";
attrResult.Add(str);
}
return attrResult.ToArray();
}
調用:
Type type = typeof(List<>);
string[] list = GetAttrs(type.GetCustomAttributesData());
foreach (var item in list)
{
Console.WriteLine(item);
}
調用時,將 Type 改成 MethodInfo 等。
輸出:
[SerializableAttribute]
[DebuggerDisplayAttribute("Count = {Count}")
[NullableAttribute((Byte)0)
[DebuggerTypeProxyAttribute(typeof(System.Collections.Generic.ICollectionDebugView`1))
[NullableContextAttribute((Byte)1)
[DefaultMemberAttribute("Item")
[TypeForwardedFromAttribute("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
2.2 委托
這里使用 2.1.4 中,解析方法的代碼。
委托中,會有很多個方法,其中有個 invoke
方法,對應定義委托時的各種信息。
/// <summary>
/// 解析委托,包括嵌套類型中的委托
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static string GetDelegateInfo(Type type)
{
if (!type.IsSubclassOf(typeof(Delegate)))
return null;
string str = "";
MethodInfo method = type.GetMethod("Invoke");
if (type.IsNested)
str += GetVisibility(method);
else
str += (type.IsPublic ? "public" : "internal") + " ";
str += type.IsSealed && type.IsAbstract ? "static " : string.Empty;
str += "delegate ";
str += GetReturn(method) + " ";
str += type.Name;
str += "(";
str += GetParams(method);
str += ")";
return str;
}
2.3 接口
上面已經解析類、抽象類、委托等,可以使用同樣的方法解析接口,然后接着解析接口的屬性、方法。
這里不再贅述。
2.4 可空類型
判斷一個類型是否為可空類型時,可以先判斷是否為泛型。
可空類型和泛型方法都可以使用 IsGenericType
屬性判斷。
GetGenericTypeDefinition
方法可以獲取泛型未綁定參數的版本。
最后判斷類型是否為 typeof(Nullable<>)
,即可完成整體解析。
/// <summary>
/// 獲取可空類型名稱
/// </summary>
/// <returns></returns>
public static string GetAbleNullName(Type type)
{
if (!type.IsGenericType)
return type.Name;
if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Type nullType = type.GetGenericArguments().FirstOrDefault();
return nullType.Name + "?";
}
return type.Name;
}