1. 反射与特性基础回顾
在C#中,反射(Reflection)是运行时检查类型信息的能力,而特性(Attribute)是为代码元素添加元数据的机制。这两个功能经常配合使用,特别是在需要动态处理类型信息的场景中。
反射的核心类是System.Type,通过它我们可以获取类型的所有信息:
- 获取类型的所有属性(PropertyInfo)
- 获取类型的所有方法(MethodInfo)
- 获取类型的所有字段(FieldInfo)
- 检查类型实现的接口
- 检查类型的基类等
特性则是通过继承System.Attribute类创建的,可以附加到各种代码元素上:
csharp复制[AttributeUsage(AttributeTargets.Property)] // 指定该特性只能用于属性
public class MyCustomAttribute : Attribute
{
public string Value { get; set; }
public MyCustomAttribute(string value)
{
Value = value;
}
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 泛型属性的反射处理
处理泛型属性时,反射需要考虑泛型类型参数的特殊性。与普通属性相比,泛型属性的PropertyInfo会有一些额外的信息需要处理。
获取泛型属性的PropertyInfo对象:
csharp复制Type type = typeof(MyGenericClass<>);
PropertyInfo property = type.GetProperty("MyGenericProperty");
判断属性是否为泛型:
csharp复制if (property.PropertyType.IsGenericType)
{
// 处理泛型属性
}
获取泛型属性的类型参数:
csharp复制Type[] genericArguments = property.PropertyType.GetGenericArguments();
foreach (Type arg in genericArguments)
{
Console.WriteLine(arg.Name);
}
3. 从泛型属性获取特性
获取特性值的基本流程是相同的,无论属性是否是泛型。关键在于正确处理PropertyInfo对象。
获取单个特性:
csharp复制MyCustomAttribute attr = (MyCustomAttribute)property
.GetCustomAttribute(typeof(MyCustomAttribute));
获取多个特性:
csharp复制IEnumerable<MyCustomAttribute> attrs = property
.GetCustomAttributes(typeof(MyCustomAttribute))
.Cast<MyCustomAttribute>();
处理可能不存在的特性:
csharp复制var attr = property.GetCustomAttribute<MyCustomAttribute>();
if (attr != null)
{
Console.WriteLine(attr.Value);
}
4. 完整示例代码
下面是一个完整的示例,展示如何通过反射获取泛型属性上的特性值:
csharp复制using System;
using System.Reflection;
// 定义自定义特性
[AttributeUsage(AttributeTargets.Property)]
public class DisplayNameAttribute : Attribute
{
public string Name { get; }
public DisplayNameAttribute(string name)
{
Name = name;
}
}
// 定义泛型类
public class GenericClass<T>
{
[DisplayName("泛型属性显示名称")]
public T GenericProperty { get; set; }
}
class Program
{
static void Main()
{
// 获取泛型类型定义
Type genericType = typeof(GenericClass<>);
// 获取泛型属性
PropertyInfo property = genericType.GetProperty("GenericProperty");
// 获取特性
DisplayNameAttribute attribute =
property.GetCustomAttribute<DisplayNameAttribute>();
if (attribute != null)
{
Console.WriteLine($"特性值: {attribute.Name}");
}
// 对于具体化的泛型类型
Type specificType = typeof(GenericClass<int>);
PropertyInfo specificProperty = specificType.GetProperty("GenericProperty");
DisplayNameAttribute specificAttribute =
specificProperty.GetCustomAttribute<DisplayNameAttribute>();
Console.WriteLine($"具体化类型的特性值: {specificAttribute.Name}");
}
}
5. 性能考虑与优化
反射操作通常比直接代码调用要慢,因此在性能敏感的场景中需要特别注意。
缓存反射结果:
csharp复制// 不好的做法:每次调用都重新获取PropertyInfo
for (int i = 0; i < 1000; i++)
{
PropertyInfo prop = typeof(MyClass).GetProperty("MyProperty");
// ...
}
// 好的做法:缓存PropertyInfo
private static readonly PropertyInfo _cachedProperty =
typeof(MyClass).GetProperty("MyProperty");
for (int i = 0; i < 1000; i++)
{
// 使用缓存的PropertyInfo
// ...
}
使用表达式树优化:
csharp复制// 创建属性访问委托
Func<object, object> CreatePropertyGetter(PropertyInfo property)
{
var instance = Expression.Parameter(typeof(object), "instance");
var cast = Expression.Convert(instance, property.DeclaringType);
var propertyAccess = Expression.Property(cast, property);
var castResult = Expression.Convert(propertyAccess, typeof(object));
return Expression.Lambda<Func<object, object>>(castResult, instance).Compile();
}
// 使用
var getter = CreatePropertyGetter(propertyInfo);
object value = getter(instance);
6. 实际应用场景
这种技术在多种场景中非常有用:
- ORM框架:自动将数据库列映射到对象属性
csharp复制[Column("user_name")]
public string UserName { get; set; }
- 序列化/反序列化:控制如何序列化属性
csharp复制[JsonProperty("name")]
public string UserName { get; set; }
- 验证框架:为属性添加验证规则
csharp复制[Required]
[MaxLength(50)]
public string UserName { get; set; }
- UI绑定:指定显示名称
csharp复制[Display(Name = "用户名")]
public string UserName { get; set; }
7. 常见问题与解决方案
问题1:获取特性时返回null
- 检查特性是否确实应用于该属性
- 确认使用的特性类型正确
- 检查AttributeUsage是否允许应用于属性
问题2:泛型类型参数不匹配
csharp复制// 对于GenericClass<T>,需要先创建具体类型
Type specificType = genericType.MakeGenericType(typeof(int));
PropertyInfo specificProperty = specificType.GetProperty("GenericProperty");
问题3:性能瓶颈
- 如前面所述,缓存反射结果
- 考虑使用表达式树或Emit生成动态方法
- 评估是否真的需要使用反射
问题4:安全考虑
csharp复制// 限制反射权限
[assembly: ReflectionPermission(SecurityAction.RequestMinimum,
Flags = ReflectionPermissionFlag.NoFlags)]
8. 高级主题:自定义特性发现逻辑
有时我们需要更灵活的特性发现机制。可以通过继承Attribute类并重写相关方法来实现。
自定义特性的匹配逻辑:
csharp复制public class MyCustomAttribute : Attribute
{
public override bool Match(object obj)
{
// 自定义匹配逻辑
return base.Match(obj);
}
}
查找继承链中的特性:
csharp复制var attribute = property.GetCustomAttribute<MyCustomAttribute>(true);
// 第二个参数true表示搜索继承链
创建自定义特性提供程序:
csharp复制public class MyAttributeProvider : ICustomAttributeProvider
{
public object[] GetCustomAttributes(bool inherit)
{
// 返回自定义特性数组
}
// 实现其他接口方法
}
9. 单元测试建议
测试反射代码时需要注意:
- 测试普通属性的特性获取
- 测试泛型属性的特性获取
- 测试不存在的特性情况
- 测试多重应用的同名特性
- 测试继承链中的特性
示例测试方法:
csharp复制[TestMethod]
public void Test_GetAttributeFromGenericProperty()
{
// 准备
Type type = typeof(GenericClass<>);
PropertyInfo property = type.GetProperty("GenericProperty");
// 执行
var attribute = property.GetCustomAttribute<DisplayNameAttribute>();
// 断言
Assert.IsNotNull(attribute);
Assert.AreEqual("泛型属性显示名称", attribute.Name);
}
10. 替代方案与比较
除了直接使用反射,还有其他方法可以获取特性信息:
- 使用预编译的反射:
csharp复制// 使用nameof和typeof在编译时获取信息
var attribute = typeof(MyClass)
.GetProperty(nameof(MyClass.MyProperty))
.GetCustomAttribute<MyAttribute>();
- 使用源代码生成器:
- 在编译时生成反射代码
- 避免运行时反射的性能开销
- 需要C# 9.0或更高版本
- 使用第三方库:
- FastMember
- HyperDescriptor
- Castle DynamicProxy
比较表:
| 方法 | 性能 | 灵活性 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 标准反射 | 低 | 高 | 低 | 通用场景 |
| 缓存反射 | 中 | 高 | 中 | 频繁调用 |
| 表达式树 | 高 | 中 | 高 | 性能敏感 |
| 源码生成 | 最高 | 低 | 高 | AOT编译 |
在实际项目中,我通常会根据具体需求选择合适的方法。对于大多数应用场景,缓存反射结果已经足够,只有在真正遇到性能瓶颈时才考虑更复杂的优化方案。
