目录html
【微信平台,此文仅受权《NCC 开源社区》订阅号发布】git
本篇主要研究类型、类型成员的各类信息和标识,经过反射的操做将信息解析出来。程序员
本文主目的的经过反射操做,生成输出相似下图的信息。数组
在此以前记一下:安全
C# 中的访问修饰符:public、private、protected、internal、protected internal。微信
C# 两个成员关键字 readonly、const。异步
C# 声明修饰符: sealed、static、virtual、new 、abstract、override。async
咱们根据反射的类型对象,大概分为:类、值类型、数组、结构体、枚举、接口、抽象类、委托、事件、各类泛型(泛型类、泛型方法、泛型构造函数等)。ide
此文花了我一天半时间,除了写文章外,查看大量文档资料,建立了不少项目,进行了大量测试验证,最终整理出来。函数
至此此系列已经进行到第九篇啦。
从 Type 中解析类型信息,笔者使用思惟导图整理如图
通常来讲,若是有两个 Type 对象,要判断两个 Type 所反射的类型,是否为同一种类型,可使用 ==
。
Type A = typeof(ClassA); Type B = typeof(ClassB); Console.WriteLine(A == B);
Type.IsClass
属性能够判断一个类型是否为类或者委托。符合条件的会有普通的类(包括泛型)、抽象类(abstract class)、委托(delegate)。
它能够排除值类型和接口。例如简单值类型、结构体、枚举、接口。
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);
获取泛型类型定义时,泛型参数的名称
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 中, 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、
new()
、notnull
。从上面看来,要解析泛型约束,不是容易的事。
可是咱们来划分一下,针对不一样状况下的组合,来理清一下 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
通过前面的操做,已经能够筛选出一个类型是否为类型或委托,那么判断一个类型是否为委托,可使用 IsSubclassOf()
,能够判断一个 Type 是否为委托类型。
IsSubclassOf()
能够判断当前 Type 是否派生于 参数中的 Type。
type.IsSubclassOf(typeof(Delegate));
另外,有个多播委托 MulticastDelegate
,可使用
type.IsSubclassOf(typeof(MulticastDelegate))
在命名空间中说明的类和委托只能使用 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
密封类是不能被继承的类型,经过 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
下面是有关于嵌套类型的 Type 的 属性。 类和委托均可以使用。
属性 | 说明 |
---|---|
IsNested | 获取一个指示当前 Type 对象是否表示其定义嵌套在另外一个类型的定义以内的类型的值。 |
IsNestedAssembly | 获取一个值,经过该值指示 Type 是不是嵌套的而且只能在它本身的程序集内可见。 |
IsNestedFamANDAssem | 获取一个值,经过该值指示 Type 是不是嵌套的而且只对同时属于本身家族和本身程序集的类可见。 |
IsNestedFamily | 获取一个值,经过该值指示 Type 是不是嵌套的而且只能在它本身的家族内可见。 |
IsNestedFamORAssem | 获取一个值,经过该值指示 Type 是不是嵌套的而且只对属于它本身的家族或属于它本身的程序集的类可见。 |
IsNestedPrivate | 获取一个值,经过该值指示 Type 是不是嵌套的并声明为私有。 |
IsNestedPublic | 获取一个值,经过该值指示类是不是嵌套的而且声明为公共的。 |
索特性的方式有两种
《C#反射与特性(七):自定义特性以及应用》中,对特性的使用作了很详细的介绍,这里再也不赘述。
属性 | 说明 |
---|---|
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); }
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() | 返回当前枚举类型中各个常数的值组成的数组。 |
Type.IsInterface
属性,判断 Type 是否为接口。
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);
复杂类型的交错数组,可使用字符串处理。
经过第一章的操做,已经能够解析程序集的大纲图了,如今开始来获取类型内部的细节,构建更为清晰的信息。
解析类型结构,过程大体以下
一个类由如下一个或多个成员组成:
成员类型 | 说明 |
---|---|
PropertyInfo | 类型的属性信息 |
FieldInfo | 类型的字段信息 |
ConstructorInfo | 类型的构造函数信息 |
MethodInfo | 类型的方法 |
ParameterInfo | 构造函数或方法的参数 |
EventInfo | 类型的事件 |
特性的话,在《C#反射与特性(七):自定义特性以及应用》已经讲解了,这里再也不赘述。
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; }
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.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
。
当咱们编写一个属性,编译时,编译器会生成对应的 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) { 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.4 重复的代码,所以这里再也不赘述,代码已经上传到码云,能够参考 解析构造函数 。
正常来讲呢,这样写属性是能够的,可是过多的修饰符对属性来讲是没意义的。
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.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()); }
解析过程是很是简单的。
咱们定义一个类型和索引器以下
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()); }
类型、方法、属性、字段等,均可以使用特性修饰,咱们要经过反射获取特性后,还要将特性结果还原出程序员写代码时设置的值。
代码以下
////// 解析输出类型、方法、属性、字段等特性 /////////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.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; }
上面已经解析类、抽象类、委托等,可使用一样的方法解析接口,而后接着解析接口的属性、方法。
这里再也不赘述。
判断一个类型是否为可空类型时,能够先判断是否为泛型。
可空类型和泛型方法均可以使用 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; }