0%

利用反射给字段赋值

在 C# 中通过反射给字段赋值是一种动态操作对象成员的常用方法,尤其适用于需要绕过编译时类型检查的场景。以下是完整的实现步骤和代码示例:


反射赋值的核心步骤

  1. 获取类型信息:通过 Type 类获取目标对象的类型元数据。
  2. 获取字段信息:通过 GetFieldGetFields 方法获取目标字段。
  3. 设置字段值:使用 FieldInfo.SetValue 方法为字段赋值。
  4. 处理类型转换:确保输入值与字段类型兼容(可能需要类型转换)。

完整代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Reflection;

public class Person
{
public string Name;
private int Age;
public static string Country = "China";
}

public class ReflectionDemo
{
public static void Main()
{
// 1. 创建对象实例
Person person = new Person();

// 2. 获取类型信息
Type type = typeof(Person);

// 3. 设置公共字段
FieldInfo nameField = type.GetField("Name");
nameField.SetValue(person, "Alice");
Console.WriteLine($"Name: {person.Name}"); // 输出: Alice

// 4. 设置私有字段(需要 BindingFlags)
FieldInfo ageField = type.GetField("Age", BindingFlags.NonPublic | BindingFlags.Instance);
ageField.SetValue(person, 30);
Console.WriteLine($"Age: {ageField.GetValue(person)}"); // 输出: 30

// 5. 设置静态字段
FieldInfo countryField = type.GetField("Country", BindingFlags.Public | BindingFlags.Static);
countryField.SetValue(null, "USA"); // 静态字段实例参数传 null
Console.WriteLine($"Country: {Person.Country}"); // 输出: USA

// 6. 动态类型转换示例
string inputValue = "42";
FieldInfo dynamicField = type.GetField("Age", BindingFlags.NonPublic | BindingFlags.Instance);
dynamicField.SetValue(person, Convert.ChangeType(inputValue, typeof(int)));
}
}

关键注意事项

  1. 字段可见性

    • 公共字段直接使用 GetField("Name")

    • 私有字段需要指定 BindingFlags

      1
      BindingFlags.NonPublic | BindingFlags.Instance
  2. 静态字段

    • 设置静态字段时,SetValue 的第一个参数传 null

      1
      countryField.SetValue(null, "USA");
  3. 类型转换

    • 如果字段类型与输入值类型不匹配,需手动转换:

      1
      Convert.ChangeType(value, targetType);
  4. 性能优化

    • 反射操作较慢,频繁调用时可缓存 FieldInfo 对象:

      1
      private static readonly FieldInfo _cachedAgeField = typeof(Person).GetField(...);
  5. 安全性

    • 修改私有字段可能破坏封装性,需谨慎使用。
    • 可通过 fieldInfo.IsInitOnly 检查字段是否为只读。

高级用法:泛型扩展方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static class ReflectionExtensions
{
public static void SetFieldValue<T>(this T obj, string fieldName, object value)
{
FieldInfo field = typeof(T).GetField(
fieldName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
);

if (field != null)
{
field.SetValue(obj, value);
}
else
{
throw new ArgumentException($"Field {fieldName} not found");
}
}
}

// 使用示例
Person person = new Person();
person.SetFieldValue("Age", 25); // 直接通过扩展方法赋值

适用场景

  1. 动态配置加载(如将 JSON/XML 数据映射到对象)。
  2. 实现通用序列化/反序列化工具。
  3. 编写测试框架时动态修改对象状态。
  4. 依赖注入容器等底层框架开发。

替代方案

  • 表达式树(Expression Trees)
    通过编译后的表达式树操作字段,性能接近直接赋值:

    1
    2
    3
    4
    5
    6
    7
    8
    var param = Expression.Parameter(typeof(Person));
    var field = Expression.Field(param, "Name");
    var setter = Expression.Lambda<Action<Person, string>>(
    Expression.Assign(field, Expression.Convert(paramValue, typeof(string))),
    param, paramValue
    ).Compile();

    setter(person, "Bob");
  • dynamic 关键字
    简单场景下快速访问公共字段(但无法访问私有成员):

    1
    2
    dynamic dynPerson = person;
    dynPerson.Name = "Charlie";

通过反射赋值提供了极大的灵活性,但需权衡性能与代码可维护性。在需要高性能的场景,推荐使用预编译的表达式树或代码生成技术(如 Source Generators)。