一、概述和前景提要
由于上文已经详细介绍过如何获取Type类型和创建实例,所以本文不再赘述,不明白的小伙伴请跳转上文:.NET进阶——深入理解反射(2)细说Type类型与实例创建一、核心定义:System.Type 是什么? System - 掘金
本文分三个大点,分别是
- 如何利用反射获取
Type类的信息 - 如何利用反射获取
ProprityInfo类的信息 - 如何利用反射获取
FieldsInfo类的信息
在此之前,小伙伴对Type类应该比较熟悉,但是对后两个ProprityInfo和FieldsInfo可能有点陌生,所以这里先具体的介绍一下反射中所用到的所有常见类:
System.Reflection.MemberInfo(抽象基类)
├── System.Type(描述类型本身,如类、接口、枚举)
├── System.Reflection.FieldInfo(描述字段)
├── System.Reflection.PropertyInfo(描述属性)
├── System.Reflection.MethodInfo(描述方法)
├── System.Reflection.ConstructorInfo(描述构造函数)
├── System.Reflection.EventInfo(描述事件)
└── System.Reflection.CustomAttributeData(描述特性数据)
1.1 System.Reflection.MemberInfo(抽象基类)
核心职责:所有反射成员的共同基类,定义了成员的通用元数据(如名称、声明类型、特性等)。
关键属性:
| 属性 | 说明 |
|---|---|
Name | 获取成员的名称(如字段名、属性名)。 |
DeclaringType | 获取声明该成员的类型(如字段 _age 声明在 Student 类中)。 |
ReflectedType | 获取通过反射获取该成员的类型(可能是子类,用于继承场景)。 |
MemberType | 获取成员的类型(枚举值:Field/Property/Method/Type 等)。 |
关键方法:
| 方法 | 说明 |
|---|---|
GetCustomAttribute<T>(bool inherit) | 获取成员上的指定类型特性(泛型版本,推荐)。 |
GetCustomAttributes(bool inherit) | 获取成员上的所有特性。 |
IsDefined(Type attributeType, bool inherit) | 检查成员是否应用了指定类型的特性。 |
1.2 System.Type(核心入口类)
核心职责:描述类型本身(如类、接口、枚举、结构体、委托),是反射的唯一入口(所有反射操作都从 Type 开始)。
获取方式:
typeof(类型名):编译时已知类型。对象.GetType():运行时已知对象实例(获取实际类型,支持多态)。Type.GetType("完全限定类名"):动态加载程序集中的类型。
关键属性:
| 属性 | 说明 |
|---|---|
Name | 类型的短名称(如 Student)。 |
FullName | 类型的完全限定名(如 MyNamespace.Student)。 |
Namespace | 类型所属的命名空间。 |
BaseType | 获取类型的直接基类(如 Student 的基类是 Person)。 |
IsClass/IsInterface/IsEnum/IsStruct | 判断类型的具体分类。 |
IsGenericType | 判断类型是否是泛型类型(如 List<T>)。 |
GetInterfaces() | 获取类型实现的所有接口。 |
关键方法:
| 方法 | 说明 |
|---|---|
GetField(string name, BindingFlags flags) | 获取指定字段(返回 FieldInfo)。 |
GetProperty(string name, BindingFlags flags) | 获取指定属性(返回 PropertyInfo)。 |
GetMethod(string name, BindingFlags flags) | 获取指定方法(返回 MethodInfo)。 |
GetConstructor(Type[] paramTypes) | 获取指定构造函数(返回 ConstructorInfo)。 |
GetMembers(BindingFlags flags) | 获取类型的所有成员(返回 MemberInfo[])。 |
CreateInstance(params object[] args) | 简化版:直接创建类型实例(内部调用构造函数)。 |
使用场景:
- 动态获取类型元数据(如类名、基类、接口)。
- 动态创建实例(如插件系统、依赖注入)。
- 动态访问类型的成员(字段、属性、方法)。
1.3 System.Reflection.FieldInfo(字段描述类)
核心职责:描述类的字段成员(如 public string Name;、private int _age;),用于动态读写字段值。
关键属性:
| 属性 | 说明 |
|---|---|
FieldType | 获取字段的类型(如 string、int)。 |
IsPublic/IsPrivate/IsProtected | 判断字段的访问修饰符。 |
IsStatic | 判断字段是否是静态字段(如 public static string School;)。 |
IsInitOnly | 判断字段是否是只读字段(readonly)。 |
IsLiteral | 判断字段是否是常量字段(const)。 |
关键方法:
| 方法 | 说明 |
|---|---|
GetValue(object obj) | 读取字段值(静态字段传 null)。 |
SetValue(object obj, object value) | 设置字段值(静态字段传 null;只读 / 常量字段无法设置)。 |
使用场景:
- 动态访问类的内部字段(如框架内部状态)。
- 序列化 / 反序列化(如 JSON 序列化时直接读写字段)。
- 测试框架中访问私有字段(如单元测试验证内部状态)。
1.4 System.Reflection.PropertyInfo(属性描述类)
核心职责:描述类的属性成员(如 public string Name { get; set; }),用于动态读写属性值,或获取 get/set 方法。
关键属性:
| 属性 | 说明 |
|---|---|
PropertyType | 获取属性的类型(如 string、int)。 |
CanRead | 判断属性是否有 get 访问器(可读)。 |
CanWrite | 判断属性是否有 set 访问器(可写)。 |
IsPublic | 判断属性的 get/set 访问器是否是公共的。 |
IsStatic | 判断属性是否是静态属性。 |
关键方法:
| 方法 | 说明 |
|---|---|
GetValue(object obj) | 读取属性值(静态属性传 null)。 |
SetValue(object obj, object value) | 设置属性值(静态属性传 null;只读属性无法设置)。 |
GetGetMethod(bool nonPublic) | 获取属性的 get 访问器(nonPublic=true 表示包含私有 get)。 |
GetSetMethod(bool nonPublic) | 获取属性的 set 访问器(nonPublic=true 表示包含私有 set)。 |
使用场景:
- 动态访问类的属性(如配置驱动的属性赋值)。
- ORM 框架映射(如 EF Core 映射数据库列到属性)。
- UI 框架数据绑定(如 WPF 的
Binding机制)。
1.5 System.Reflection.MethodInfo(方法描述类)
核心职责:描述类的方法成员(如实例方法、静态方法、泛型方法),用于动态调用方法。
关键属性:
| 属性 | 说明 |
|---|---|
ReturnType | 获取方法的返回值类型。 |
IsPublic/IsPrivate | 判断方法的访问修饰符。 |
IsStatic | 判断方法是否是静态方法。 |
IsGenericMethod | 判断方法是否是泛型方法(如 T GetDefault<T>())。 |
GetParameters() | 获取方法的参数列表(返回 ParameterInfo[])。 |
关键方法:
| 方法 | 说明 |
|---|---|
Invoke(object obj, object[] parameters) | 调用方法(实例方法传 obj,静态方法传 null;无参数传 null)。 |
MakeGenericMethod(Type[] typeArgs) | 创建泛型方法实例(如 GetDefault<int>())。 |
GetGenericArguments() | 获取泛型方法的类型参数(如 T)。 |
使用场景:
- 动态调用方法(如插件系统中的扩展方法)。
- 事件处理(如动态绑定事件处理器)。
- 泛型方法的动态调用(如
List<T>.Add(T item))。
二、如何利用反射获取 Type 类的信息
Type 类是反射的核心入口,通过它可以获取类型的完整元数据,包括名称、基类、接口、成员数量、泛型信息等。以下是具体获取方式和示例:
2.1 获取类型的基本信息
通过 Type 的属性可直接获取类型的标识信息(如名称、命名空间)和分类信息(如是否是类、接口)。
关键属性
| 属性 | 说明 |
|---|---|
Name/FullName | 短名称 / 完全限定名 |
Namespace | 命名空间 |
BaseType | 直接基类 |
IsClass/IsInterface/IsEnum | 类型分类 |
IsGenericType | 是否是泛型类型 |
示例代码
// 定义测试类型
public class Student : Person, IStudy
{
// 成员略
}
public class Person { }
public interface IStudy { }
// 获取Type对象
Type studentType = typeof(Student);
// 1. 基本标识信息
Console.WriteLine($"短名称:{studentType.Name}"); // 输出:Student
Console.WriteLine($"完全限定名:{studentType.FullName}"); // 输出:MyNamespace.Student
Console.WriteLine($"命名空间:{studentType.Namespace}"); // 输出:MyNamespace
// 2. 继承与实现信息
Console.WriteLine($"基类:{studentType.BaseType?.Name}"); // 输出:Person
Console.WriteLine("实现的接口:");
foreach (Type iface in studentType.GetInterfaces())
{
Console.WriteLine($"- {iface.Name}"); // 输出:IStudy
}
// 3. 类型分类信息
Console.WriteLine($"是否是类:{studentType.IsClass}"); // 输出:True
Console.WriteLine($"是否是接口:{studentType.IsInterface}"); // 输出:False
Console.WriteLine($"是否是泛型:{studentType.IsGenericType}"); // 输出:False
Console.WriteLine($"是否是值类型:{studentType.IsValueType}"); // 输出:False
2.2 获取类型的成员概览
通过 Type.GetMembers() 可获取类型的所有成员(字段、属性、方法等),并通过 MemberType 区分成员类型。
示例代码
// 定义测试类
public class Student
{
public string Name; // 字段
public int Age { get; set; } // 属性
public void Study() { } // 方法
public Student() { } // 构造函数
}
Type studentType = typeof(Student);
// 获取所有公共成员
MemberInfo[] members = studentType.GetMembers();
Console.WriteLine($"Student类共有 {members.Length} 个公共成员:");
foreach (MemberInfo member in members)
{
// 根据MemberType区分成员类型
string memberType = member.MemberType switch
{
MemberTypes.Field => "字段",
MemberTypes.Property => "属性",
MemberTypes.Method => "方法",
MemberTypes.Constructor => "构造函数",
_ => member.MemberType.ToString()
};
Console.WriteLine($"- {memberType}:{member.Name}");
}
输出示例:
Student类共有 11 个公共成员:
- 构造函数:.ctor
- 方法:Equals
- 方法:GetHashCode
- 方法:GetType
- 方法:Study
- 方法:ToString
- 字段:Name
- 属性:Age
2.3 获取泛型类型的信息
对于泛型类型(如 List<T>),可通过 Type 获取泛型参数和构造后的具体类型。
示例代码
// 获取泛型类型 List<T>
Type genericListType = typeof(List<>);
Console.WriteLine($"泛型类型:{genericListType.Name}"); // 输出:List`1(`1表示1个泛型参数)
Console.WriteLine($"是否是泛型定义:{genericListType.IsGenericTypeDefinition}"); // 输出:True
// 获取泛型参数
Type[] genericParams = genericListType.GetGenericArguments();
Console.WriteLine($"泛型参数数量:{genericParams.Length}"); // 输出:1
foreach (Type param in genericParams)
{
Console.WriteLine($"- 泛型参数:{param.Name}"); // 输出:T
}
// 构造具体泛型类型 List<int>
Type intListType = genericListType.MakeGenericType(typeof(int));
Console.WriteLine($"构造后的类型:{intListType.FullName}"); // 输出:System.Collections.Generic.List`1[[System.Int32, ...]]
三、如何利用反射获取 PropertyInfo 类的信息
PropertyInfo 用于描述属性的元数据,通过 Type.GetProperty() 或 Type.GetProperties() 获取,可进一步获取属性的类型、访问器、特性等信息。
3.1 获取单个属性的信息
通过 Type.GetProperty(string name) 获取指定名称的属性,结合 BindingFlags 可获取私有 / 静态属性。
示例代码
// 定义测试类
public class Student
{
public string Name { get; set; } // 公共可读可写属性
private int _age;
public int Age // 封装私有字段的属性
{
get => _age;
private set => _age = value; // 私有set访问器
}
public static string School { get; set; } = "XX中学"; // 静态属性
}
Type studentType = typeof(Student);
// 1. 获取公共属性 Name
PropertyInfo nameProp = studentType.GetProperty("Name");
Console.WriteLine($"属性名:{nameProp.Name}"); // 输出:Name
Console.WriteLine($"属性类型:{nameProp.PropertyType.Name}"); // 输出:String
Console.WriteLine($"是否可读:{nameProp.CanRead}"); // 输出:True
Console.WriteLine($"是否可写:{nameProp.CanWrite}"); // 输出:True
// 2. 获取带私有set访问器的属性 Age
PropertyInfo ageProp = studentType.GetProperty("Age");
Console.WriteLine($"Age是否可写:{ageProp.CanWrite}"); // 输出:True(即使set是私有,CanWrite仍为True)
// 获取Age的get/set访问器
MethodInfo getAgeMethod = ageProp.GetGetMethod(); // 公共get访问器
MethodInfo setAgeMethod = ageProp.GetSetMethod(); // 私有set访问器,返回null
MethodInfo privateSetAgeMethod = ageProp.GetSetMethod(nonPublic: true); // 传入true获取私有set
Console.WriteLine($"Age的get访问器:{getAgeMethod?.Name}"); // 输出:get_Age
Console.WriteLine($"Age的私有set访问器:{privateSetAgeMethod?.Name}"); // 输出:set_Age
// 3. 获取静态属性 School
PropertyInfo schoolProp = studentType.GetProperty("School", BindingFlags.Public | BindingFlags.Static);
Console.WriteLine($"静态属性School的值:{schoolProp.GetValue(null)}"); // 输出:XX中学(静态属性传null)
3.2 获取多个属性的信息
通过 Type.GetProperties() 获取类型的所有匹配属性,结合 BindingFlags 可筛选特定条件的属性(如仅公共属性、仅实例属性)。
示例代码
Type studentType = typeof(Student);
// 1. 获取所有公共实例属性(默认行为)
PropertyInfo[] publicProps = studentType.GetProperties();
Console.WriteLine($"公共实例属性数量:{publicProps.Length}"); // 输出:2(Name、Age)
// 2. 获取所有公共属性(包含静态)
PropertyInfo[] allPublicProps = studentType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
Console.WriteLine($"所有公共属性数量:{allPublicProps.Length}"); // 输出:3(Name、Age、School)
// 3. 遍历属性并输出详细信息
Console.WriteLine("\n所有公共属性详情:");
foreach (PropertyInfo prop in allPublicProps)
{
string accessors = $"{(prop.CanRead ? "get" : "")}{(prop.CanWrite ? "set" : "")}";
string isStatic = prop.GetGetMethod()?.IsStatic ?? prop.GetSetMethod()?.IsStatic ?? false ? "(静态)" : "";
Console.WriteLine($"- {prop.Name}:{prop.PropertyType.Name},访问器:{accessors}{isStatic}");
}
输出示例:
公共实例属性数量:2
所有公共属性数量:3
所有公共属性详情:
- Name:String,访问器:getset
- Age:Int32,访问器:getset
- School:String,访问器:getset(静态)
3.3 获取属性的特性信息
通过 PropertyInfo.GetCustomAttribute<T>() 可获取属性上应用的特性,常用于框架开发(如 ORM 映射、API 验证)。
示例代码
// 定义特性
[AttributeUsage(AttributeTargets.Property)]
public class DisplayAttribute : Attribute
{
public string Name { get; set; }
public int Order { get; set; }
}
// 应用特性的测试类
public class Student
{
[Display(Name = "学生姓名", Order = 1)]
public string Name { get; set; }
[Display(Name = "学生年龄", Order = 2)]
public int Age { get; set; }
}
Type studentType = typeof(Student);
PropertyInfo[] props = studentType.GetProperties();
foreach (PropertyInfo prop in props)
{
// 获取Display特性
DisplayAttribute displayAttr = prop.GetCustomAttribute<DisplayAttribute>();
if (displayAttr != null)
{
Console.WriteLine($"属性:{prop.Name}");
Console.WriteLine($"- 显示名称:{displayAttr.Name}");
Console.WriteLine($"- 排序:{displayAttr.Order}");
}
}
输出示例:
属性:Name
- 显示名称:学生姓名
- 排序:1
属性:Age
- 显示名称:学生年龄
- 排序:2
四、如何利用反射获取 FieldInfo 类的信息
FieldInfo 用于描述字段的元数据,通过 Type.GetField() 或 Type.GetFields() 获取,可进一步获取字段的类型、访问修饰符、是否只读等信息。
4.1 获取单个字段的信息
通过 Type.GetField(string name) 获取指定名称的字段,结合 BindingFlags 可获取私有 / 静态字段。
示例代码
// 定义测试类
public class Student
{
public string Name; // 公共实例字段
private int _age; // 私有实例字段
public readonly string Id = Guid.NewGuid().ToString(); // 只读字段
public static string School = "XX中学"; // 静态字段
public const string Country = "中国"; // 常量字段
}
Type studentType = typeof(Student);
// 1. 获取公共实例字段 Name
FieldInfo nameField = studentType.GetField("Name");
Console.WriteLine($"字段名:{nameField.Name}"); // 输出:Name
Console.WriteLine($"字段类型:{nameField.FieldType.Name}"); // 输出:String
Console.WriteLine($"是否公开:{nameField.IsPublic}"); // 输出:True
Console.WriteLine($"是否静态:{nameField.IsStatic}"); // 输出:False
// 2. 获取私有实例字段 _age(需指定BindingFlags)
FieldInfo ageField = studentType.GetField("_age", BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine($"私有字段_age的类型:{ageField.FieldType.Name}"); // 输出:Int32
// 3. 获取只读字段 Id
FieldInfo idField = studentType.GetField("Id");
Console.WriteLine($"Id是否只读:{idField.IsInitOnly}"); // 输出:True(readonly字段)
// 4. 获取常量字段 Country
FieldInfo countryField = studentType.GetField("Country");
Console.WriteLine($"Country是否常量:{countryField.IsLiteral}"); // 输出:True(const字段)
Console.WriteLine($"常量值:{countryField.GetValue(null)}"); // 输出:中国(常量字段传null)
// 5. 获取静态字段 School
FieldInfo schoolField = studentType.GetField("School", BindingFlags.Public | BindingFlags.Static);
Console.WriteLine($"静态字段School的值:{schoolField.GetValue(null)}"); // 输出:XX中学
4.2 获取多个字段的信息
通过 Type.GetFields() 获取类型的所有匹配字段,结合 BindingFlags 可筛选特定条件的字段(如仅私有字段、仅静态字段)。
示例代码
Type studentType = typeof(Student);
// 1. 获取所有公共实例字段(默认行为)
FieldInfo[] publicFields = studentType.GetFields();
Console.WriteLine($"公共实例字段数量:{publicFields.Length}"); // 输出:2(Name、Id)
// 2. 获取所有字段(公共+私有+静态+实例)
FieldInfo[] allFields = studentType.GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
Console.WriteLine($"所有字段数量:{allFields.Length}"); // 输出:5(Name、_age、Id、School、Country)
// 3. 遍历字段并输出详细信息
Console.WriteLine("\n所有字段详情:");
foreach (FieldInfo field in allFields)
{
// 访问修饰符
string accessModifier = field.IsPublic ? "public" :
field.IsPrivate ? "private" :
field.IsProtected ? "protected" : "internal";
// 静态/实例
string scope = field.IsStatic ? "static" : "instance";
// 只读/常量
string modifier = field.IsLiteral ? "const" :
field.IsInitOnly ? "readonly" : "";
Console.WriteLine($"- {accessModifier} {scope} {modifier} {field.FieldType.Name} {field.Name}");
}
输出示例:
公共实例字段数量:2
所有字段数量:5
所有字段详情:
- public instance String Name
- private instance Int32 _age
- public instance readonly String Id
- public static String School
- public static const String Country
4.3 动态读写字段值
通过 FieldInfo.GetValue() 和 FieldInfo.SetValue() 可动态操作字段值,常用于序列化、测试框架等场景。
示例代码
// 创建实例
Student stu = new Student { Name = "张三" };
// 获取字段信息
FieldInfo nameField = typeof(Student).GetField("Name");
FieldInfo ageField = typeof(Student).GetField("_age", BindingFlags.NonPublic | BindingFlags.Instance);
// 读取字段值
string name = (string)nameField.GetValue(stu);
int age = (int)ageField.GetValue(stu);
Console.WriteLine($"初始值:Name={name}, _age={age}"); // 输出:初始值:Name=张三, _age=0
// 修改字段值
nameField.SetValue(stu, "李四");
ageField.SetValue(stu, 18);
// 再次读取
name = (string)nameField.GetValue(stu);
age = (int)ageField.GetValue(stu);
Console.WriteLine($"修改后:Name={name}, _age={age}"); // 输出:修改后:Name=李四, _age=18
五、总结
反射获取这些信息是非常重要的,对我们理解框架和库有非常重要的帮助,下一篇博客会着重介绍如何获取方法、特性的信息