C#反射與特性(九):全網最全-解析反射


目錄html


目錄

【微信平臺,此文僅受權《NCC 開源社區》訂閱號發佈】git

本篇主要研究類型、類型成員的各類信息和標識,經過反射的操做將信息解析出來。程序員

本文主目的的經過反射操做,生成輸出相似下圖的信息。數組

在此以前記一下:安全

C# 中的訪問修飾符:public、private、protected、internal、protected internal。微信

C# 兩個成員關鍵字 readonly、const。異步

C# 聲明修飾符: sealed、static、virtual、new 、abstract、override。async

咱們根據反射的類型對象,大概分爲:類、值類型、數組、結構體、枚舉、接口、抽象類、委託、事件、各類泛型(泛型類、泛型方法、泛型構造函數等)。ide

此文花了我一天半時間,除了寫文章外,查看大量文檔資料,建立了不少項目,進行了大量測試驗證,最終整理出來。函數

至此此係列已經進行到第九篇啦。

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);

    public class ClassC{
        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);
            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);
            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{ }
            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 中, GetGenericParameterConstraintsGenericParameterAttributes 屬性,能夠判斷約束類型。

約束 描述
where T : struct 值類型
where T : class 類型參數必須是引用類型。 此約束還應用於任何類、接口、委託或數組類型
where T : notnull 類型參數必須是不可爲 null 的類型
where T : unmanaged 類型參數必須是不可爲 null 的非託管類型,跟struct十分類似,可是unmanaged是不安全的。
where T : new() 類型參數必須具備公共無參數構造函數。 與其餘約束一塊兒使用時,new() 約束必須最後指定。 new() 約束不能與 structunmanaged 約束結合使用。
where T : 類型參數必須是指定的基類或派生自指定的基類
where T : 類型參數必須是指定的接口或實現指定的接口。 可指定多個接口約束。 約束接口也能夠是泛型。
where T : U 爲 T 提供的類型參數必須是爲 U 提供的參數或派生自爲 U 提供的參數

GetGenericParameterConstraints 能夠獲取到參數類型,不過只能對 struct、class、

  • 這些約束之間,有着複雜的衝突關係,這裏就不一一列舉了。
  • 而有些類型自己就表明包含多種約束,例如 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 的兩個屬性 IsPublicIsNotPublic能夠對此進行識別。

測試:

    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 任意兩個不能在一塊兒調用。

若是一個類是靜態類,那麼 IsSealedIsAbstract 都是 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# 關鍵字 protectedinternal 在 IL 中沒有任何意義,且不會用於反射 API 中。也就是說在反射中看來,這兩個訪問修飾符沒做用;不過對於獲取信息來講,仍是須要想辦法解析。

protected、internal、protected internal 對於反射調用來講,是沒有意義的,不過對於獲取信息來講,仍是須要想辦法解析。

判斷是否爲 internal  可使用 IsAssembly ;判斷是否爲 protected internal ,可使用IsFamilyOrAssembly ;兩個屬性一塊兒用,結果都是 false 的話,則是 protected

屬性 說明
IsAssembly 是否爲 internal
IsFamily 是否爲 protected
IsFamilyOrAssembly 判斷是否爲 protected internal

注: protected internalinternal 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);
            IEnumerablefields1 = 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 。

如下屬性能夠區分修飾符:

IsAbstractIsVirtualIsHideBySigIsFinal

virtual、override、abstract、new 修飾的方法,IsHideBySig  結果都是 true,可用此屬性判斷方法是否有抽象、重寫等關鍵字修飾。

對於 virtual、override 修飾的方法,IsVirtual  爲 true,new 修飾的方法 IsVirtual  爲 flase。

可是一個方法,若是是實現了接口方法的話,使用 IsVirtual 也會返回 trueIsHideBySig  也會返回 true。

那麼就剩下區分 virtualoverride 了,若是當前方法是重寫了父類的,使用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";
            }
        }
獲取返回類型

能夠從  ReturnParameterReturnTypeReturnTypeCustomAttributes 獲取有關返回類型的信息。

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.ReturnTypemethod.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)
        {
            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 )
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 獲取特性

類型、方法、屬性、字段等,均可以使用特性修飾,咱們要經過反射獲取特性後,還要將特性結果還原出程序員寫代碼時設置的值。

代碼以下

        ////// 解析輸出類型、方法、屬性、字段等特性
        /////////public static string[] GetAttrs(IListattrs)
        {
            ListattrResult = new List(); ;
            foreach (var item in attrs)
            {
                Type attrType = item.GetType();
                string str = "[";
                str += item.AttributeType.Name;
                // 構造函數中的值
                IListcustoms = item.ConstructorArguments;
                // 屬性的值
                IListarguments = 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 方法,對應定義委託時的各類信息。

        ////// 解析委託,包括嵌套類型中的委託
        /////////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<>) ,便可完成總體解析。

        ////// 獲取可空類型名稱
        //////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;
        }
相關文章
相關標籤/搜索