第 10 章:反射与 Source Generator
jerry北京市2026年4月21日C# 5 次阅读 约 27 分钟

深入理解反射的原理与性能代价、Attribute 机制、动态代理模式,以及 Source Generator 如何在编译时生成代码来替代运行时反射。
10.1 反射基础
反射允许在运行时检查和操作类型信息:
// 获取 Type 对象
Type type = typeof(List<int>); // 编译时已知类型
Type type2 = obj.GetType(); // 运行时获取
Type type3 = Type.GetType("System.String"); // 通过名称获取
// 检查类型信息
Console.WriteLine(type.Name); // List`1
Console.WriteLine(type.FullName); // System.Collections.Generic.List`1[System.Int32]
Console.WriteLine(type.IsGenericType); // True
Console.WriteLine(type.IsValueType); // False
Console.WriteLine(type.BaseType); // System.Object
获取成员信息
class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
private int _age;
public void SayHello(string greeting) => Console.WriteLine($"{greeting}, {Name}");
}
Type type = typeof(User);
// 属性
PropertyInfo[] props = type.GetProperties();
foreach (var prop in props)
Console.WriteLine($"{prop.Name}: {prop.PropertyType.Name}");
// Id: Int32
// Name: String
// 方法
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (var method in methods)
Console.WriteLine($"{method.Name}({string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name))})");
// 私有字段
FieldInfo? field = type.GetField("_age", BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine(field?.Name); // _age
动态调用
// 创建实例
object instance = Activator.CreateInstance(typeof(User))!;
// 设置属性
PropertyInfo nameProp = typeof(User).GetProperty("Name")!;
nameProp.SetValue(instance, "张三");
// 调用方法
MethodInfo method = typeof(User).GetMethod("SayHello")!;
method.Invoke(instance, ["你好"]); // 输出:你好, 张三
// 访问私有字段
FieldInfo ageField = typeof(User).GetField("_age", BindingFlags.NonPublic | BindingFlags.Instance)!;
ageField.SetValue(instance, 25);
Console.WriteLine(ageField.GetValue(instance)); // 25
10.2 反射的性能代价
反射比直接调用慢 几十到几百倍:
// BenchmarkDotNet 测试
[MemoryDiagnoser]
public class ReflectionBenchmark
{
private readonly User _user = new() { Name = "Test" };
private readonly PropertyInfo _prop = typeof(User).GetProperty("Name")!;
private readonly Func<User, string> _compiled;
public ReflectionBenchmark()
{
// 预编译的委托
_compiled = (Func<User, string>)Delegate.CreateDelegate(
typeof(Func<User, string>), _prop.GetGetMethod()!);
}
[Benchmark(Baseline = true)]
public string Direct() => _user.Name; // ~0.5ns
[Benchmark]
public object? Reflection() => _prop.GetValue(_user); // ~100ns
[Benchmark]
public string Compiled() => _compiled(_user); // ~1ns
}
优化反射性能
// 方法 1:缓存 PropertyInfo / MethodInfo
static readonly PropertyInfo NameProp = typeof(User).GetProperty("Name")!;
// 方法 2:使用 Delegate.CreateDelegate
var getter = (Func<User, string>)Delegate.CreateDelegate(
typeof(Func<User, string>), typeof(User).GetProperty("Name")!.GetGetMethod()!);
// 方法 3:使用表达式树编译
static Func<T, TValue> CreateGetter<T, TValue>(string propertyName)
{
var param = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(param, propertyName);
return Expression.Lambda<Func<T, TValue>>(property, param).Compile();
}
var fastGetter = CreateGetter<User, string>("Name");
// 方法 4:.NET 7+ 使用 UnsafeAccessor(零开销访问私有成员)
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_age")]
static extern ref int GetAge(User user);
var user = new User();
GetAge(user) = 25; // 直接访问私有字段,无反射开销
10.3 Attribute(特性)
内置特性
[Obsolete("使用 NewMethod 代替", error: false)]
public void OldMethod() { }
[Conditional("DEBUG")]
public void DebugOnly() { }
[Serializable]
public class MyData { }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int FastMethod() => 42;
自定义特性
// 定义特性
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
class ValidateRangeAttribute : Attribute
{
public int Min { get; }
public int Max { get; }
public string? ErrorMessage { get; set; }
public ValidateRangeAttribute(int min, int max)
{
Min = min;
Max = max;
}
}
// 使用特性
class Product
{
[ValidateRange(0, 10000, ErrorMessage = "价格必须在 0-10000 之间")]
public decimal Price { get; set; }
[ValidateRange(1, 999)]
public int Quantity { get; set; }
}
// 通过反射读取特性并验证
static List<string> Validate(object obj)
{
var errors = new List<string>();
foreach (var prop in obj.GetType().GetProperties())
{
var attr = prop.GetCustomAttribute<ValidateRangeAttribute>();
if (attr is null) continue;
var value = Convert.ToDecimal(prop.GetValue(obj));
if (value < attr.Min || value > attr.Max)
{
errors.Add(attr.ErrorMessage ?? $"{prop.Name} 超出范围 [{attr.Min}, {attr.Max}]");
}
}
return errors;
}
10.4 动态代理模式
使用 DispatchProxy 实现 AOP(面向切面编程):
// 接口
interface IUserService
{
User GetById(int id);
void Save(User user);
}
// 实现
class UserService : IUserService
{
public User GetById(int id) => new() { Id = id, Name = "张三" };
public void Save(User user) => Console.WriteLine($"保存: {user.Name}");
}
// 动态代理:自动添加日志
class LoggingProxy<T> : DispatchProxy where T : class
{
private T _target = default!;
public static T Create(T target)
{
var proxy = Create<T, LoggingProxy<T>>() as LoggingProxy<T>;
proxy!._target = target;
return (proxy as T)!;
}
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
Console.WriteLine($"[调用] {targetMethod!.Name}({string.Join(", ", args ?? [])})");
var sw = Stopwatch.StartNew();
try
{
var result = targetMethod.Invoke(_target, args);
Console.WriteLine($"[完成] {targetMethod.Name} 耗时 {sw.ElapsedMilliseconds}ms");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"[异常] {targetMethod.Name}: {ex.Message}");
throw;
}
}
}
// 使用
IUserService service = LoggingProxy<IUserService>.Create(new UserService());
service.GetById(1);
// [调用] GetById(1)
// [完成] GetById 耗时 0ms
10.5 Source Generator
Source Generator 在编译时生成代码,替代运行时反射:
IIncrementalGenerator(推荐,.NET 6+)
// 1. 创建一个 .NET Standard 2.0 类库项目
// 2. 添加 NuGet 包:Microsoft.CodeAnalysis.CSharp
[Generator]
public class AutoToStringGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 筛选带有 [AutoToString] 特性的类
var classDeclarations = context.SyntaxProvider
.ForAttributeWithMetadataName(
"AutoToStringAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx))
.Where(info => info is not null);
// 注册代码生成
context.RegisterSourceOutput(classDeclarations, (spc, classInfo) =>
{
var source = GenerateToString(classInfo!);
spc.AddSource($"{classInfo!.ClassName}_ToString.g.cs", source);
});
}
private static ClassInfo? GetClassInfo(GeneratorAttributeSyntaxContext context)
{
var symbol = context.TargetSymbol as INamedTypeSymbol;
if (symbol is null) return null;
var properties = symbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.DeclaredAccessibility == Accessibility.Public)
.Select(p => p.Name)
.ToList();
return new ClassInfo(symbol.Name, symbol.ContainingNamespace.ToDisplayString(), properties);
}
private static string GenerateToString(ClassInfo info)
{
var props = string.Join(", ", info.Properties.Select(p => $"{p} = {{{p}}}"));
return $$"""
namespace {{info.Namespace}};
partial class {{info.ClassName}}
{
public override string ToString()
=> $"{{info.ClassName}} { {{props}} }";
}
""";
}
record ClassInfo(string ClassName, string Namespace, List<string> Properties);
}
使用 Source Generator
// 标记特性
[AttributeUsage(AttributeTargets.Class)]
public class AutoToStringAttribute : Attribute { }
// 使用
[AutoToString]
public partial class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
}
// 编译时自动生成:
// public override string ToString()
// => $"Product { Id = {Id}, Name = {Name}, Price = {Price} }";
var product = new Product { Id = 1, Name = "手机", Price = 4999 };
Console.WriteLine(product); // Product { Id = 1, Name = 手机, Price = 4999 }
10.6 反射 vs Source Generator
| 特性 | 反射 | Source Generator |
|---|---|---|
| 执行时机 | 运行时 | 编译时 |
| 性能 | 较慢(运行时开销) | 零运行时开销 |
| AOT 兼容 | 部分不兼容 | 完全兼容 |
| 调试 | 运行时才能发现错误 | 编译时报错 |
| 灵活性 | 高(可处理未知类型) | 只能处理编译时已知的代码 |
| 典型应用 | ORM、序列化、DI | JSON 序列化、日志、验证 |
.NET 的趋势是用 Source Generator 替代反射:
System.Text.Json:Source Generator 序列化LoggerMessage:编译时日志生成RegexGenerator:编译时正则编译
// System.Text.Json Source Generator 示例
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(List<User>))]
partial class AppJsonContext : JsonSerializerContext { }
// 使用(零反射,AOT 友好)
var json = JsonSerializer.Serialize(user, AppJsonContext.Default.User);
var user = JsonSerializer.Deserialize(json, AppJsonContext.Default.User);
10.7 面试要点
-
反射的性能代价?
- 比直接调用慢几十到几百倍,涉及类型查找、安全检查、装箱等
- 优化方式:缓存 MemberInfo、Delegate.CreateDelegate、表达式树编译
-
Attribute 是什么?如何自定义?
- 编译时附加到代码元素上的元数据,通过反射在运行时读取
- 继承 Attribute 类,用 AttributeUsage 控制使用范围
-
Source Generator 和反射的区别?
- Source Generator 在编译时生成代码,零运行时开销,AOT 友好
- 反射在运行时检查类型,灵活但有性能代价
-
DispatchProxy 的原理?
- 运行时生成代理类,重写 Invoke 方法拦截所有调用
-
.NET 中哪些框架使用了 Source Generator?
- System.Text.Json、LoggerMessage、RegexGenerator、Minimal API 等
练习
- 使用反射实现一个简单的对象映射器(类似 AutoMapper)
- 使用 DispatchProxy 实现一个缓存代理(自动缓存方法返回值)
- 创建一个 Source Generator,为标记了特性的类自动生成 Builder 模式代码
- 对比反射调用、Delegate.CreateDelegate、表达式树编译的性能
评论
登录 后发表评论
暂无评论