.NET进阶——深入理解反射(3)利用反射获取信息(对象、属性、字段)

8 阅读9分钟

一、概述和前景提要

由于上文已经详细介绍过如何获取Type类型和创建实例,所以本文不再赘述,不明白的小伙伴请跳转上文:.NET进阶——深入理解反射(2)细说Type类型与实例创建一、核心定义:System.Type 是什么? System - 掘金

本文分三个大点,分别是

  • 如何利用反射获取Type类的信息
  • 如何利用反射获取ProprityInfo类的信息
  • 如何利用反射获取FieldsInfo类的信息

在此之前,小伙伴对Type类应该比较熟悉,但是对后两个ProprityInfoFieldsInfo可能有点陌生,所以这里先具体的介绍一下反射中所用到的所有常见类:

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获取字段的类型(如 stringint)。
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获取属性的类型(如 stringint)。
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

五、总结

反射获取这些信息是非常重要的,对我们理解框架和库有非常重要的帮助,下一篇博客会着重介绍如何获取方法、特性的信息