第 10 章:反射与 Source Generator

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

深入理解反射的原理与性能代价、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 面试要点

  1. 反射的性能代价?

    • 比直接调用慢几十到几百倍,涉及类型查找、安全检查、装箱等
    • 优化方式:缓存 MemberInfo、Delegate.CreateDelegate、表达式树编译
  2. Attribute 是什么?如何自定义?

    • 编译时附加到代码元素上的元数据,通过反射在运行时读取
    • 继承 Attribute 类,用 AttributeUsage 控制使用范围
  3. Source Generator 和反射的区别?

    • Source Generator 在编译时生成代码,零运行时开销,AOT 友好
    • 反射在运行时检查类型,灵活但有性能代价
  4. DispatchProxy 的原理?

    • 运行时生成代理类,重写 Invoke 方法拦截所有调用
  5. .NET 中哪些框架使用了 Source Generator?

    • System.Text.Json、LoggerMessage、RegexGenerator、Minimal API 等

练习

  1. 使用反射实现一个简单的对象映射器(类似 AutoMapper)
  2. 使用 DispatchProxy 实现一个缓存代理(自动缓存方法返回值)
  3. 创建一个 Source Generator,为标记了特性的类自动生成 Builder 模式代码
  4. 对比反射调用、Delegate.CreateDelegate、表达式树编译的性能

← 上一章:委托、事件与表达式树 | 下一章:泛型的底层实现 →

评论

登录 后发表评论

暂无评论