C#实现方法前后置包裹逻辑、构造函数后自动初始化方案咨询
原生C#特性(Attribute)本身只是附加在程序集、类型、成员上的元数据标记,不具备自主触发逻辑的能力,你需要配合额外的织入逻辑实现需求,以下是三种按侵入性从低到高排序的实现方式,都能满足你要的前后置逻辑包裹、构造完成自动调用Initialize()的需求:
1. 动态代理实现(最贴合你预期的写法,侵入性极低)
绝大多数.NET项目都会使用依赖注入容器,你可以基于动态代理实现无侵入的AOP(面向切面编程)逻辑,不需要修改现有业务方法的代码,完全支持你设想的[mywrapperFunction("spam")]标记写法:
- 实例被代理创建时,会在构造函数执行完毕后自动调用
Initialize()方法 - 拦截所有被标记的方法,自动执行前置、后置逻辑
- 不需要手动写大量包裹函数,也不需要修改方法内部实现
如果是.NET Core/.NET 5+项目,可以直接用框架自带的DispatchProxy实现,不需要引入第三方依赖,示例代码如下:
// 自定义标记特性,和你设想的写法完全一致 [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true)] public class WrapperAttribute : Attribute { public string Tag { get; } public WrapperAttribute(string tag) => Tag = tag; } // 通用代理实现 public class ServiceProxy<T> : DispatchProxy where T : class { private T _targetInstance = default!; public static T Create(T instance) { var proxy = Create<T, ServiceProxy<T>>() as ServiceProxy<T>; proxy!._targetInstance = instance; // 构造函数执行完成,自动调用Initialize方法 var initMethod = typeof(T).GetMethod("Initialize", Type.EmptyTypes); initMethod?.Invoke(instance, null); return proxy as T; } protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) { // 检查方法或所属类是否有包裹标记 var wrapperMark = targetMethod?.GetCustomAttribute<WrapperAttribute>() ?? typeof(T).GetCustomAttribute<WrapperAttribute>(); if (wrapperMark == null) { // 无标记直接执行原方法 return targetMethod?.Invoke(_targetInstance, args); } // 前置逻辑 Console.WriteLine($"{wrapperMark.Tag} before"); var tic = DateTime.Now; // 执行原方法 var result = targetMethod?.Invoke(_targetInstance, args); // 后置逻辑 var cost = DateTime.Now - tic; Console.WriteLine($"{wrapperMark.Tag} after in {cost.TotalMilliseconds}ms"); return result; } }
使用时只需要在依赖注入注册时,将原有服务替换为代理返回即可,所有从容器解析的实例都会自动走包裹逻辑:
// 原注册方式:services.AddScoped<IFooService, FooService>(); // 替换为代理注册 services.AddScoped<IFooService>(sp => { var realInstance = new FooService(); // 也可从服务容器解析依赖 return ServiceProxy<IFooService>.Create(realInstance); });
业务代码里的写法和你预期完全一致:
[Wrapper("spam")] public void Foo() { Console.WriteLine("inside foo"); }
如果需要代理没有实现接口的普通类,可以使用Castle DynamicProxy库实现,用法和上面的示例基本一致,功能更全面。
2. 编译时织入(性能最优,零运行时开销)
如果对性能要求较高,可以使用基于Roslyn的源生成器做编译时AOP:在项目编译阶段直接把前后置逻辑、构造函数末尾的Initialize()调用织入到编译生成的IL代码中,没有运行时反射、代理类的开销。
这种方式同样支持Attribute标记的写法,甚至不需要修改依赖注入注册逻辑,不管是通过容器解析还是直接new出来的实例,都会自动执行你要的逻辑,适合性能敏感的大规模项目使用。
3. 泛型工厂+通用包裹方法(零依赖,逻辑完全可控)
如果不想引入AOP相关的能力,也可以用泛型实现通用的工厂和包裹方法,只需要写两个泛型方法就能覆盖所有返回值、参数类型的场景,不需要写大量重载:
// 所有需要自动初始化的类实现该空接口即可 public interface IAutoInitializable { void Initialize(); } public static class ObjectFactory { // 创建实例后自动调用Initialize public static T Create<T>() where T : IAutoInitializable, new() { var instance = new T(); instance.Initialize(); return instance; } // 无返回值方法的通用包裹 public static void RunWithWrap<T>(this T instance, Action<T> action, string tag) { Console.WriteLine($"{tag} before"); var tic = DateTime.Now; action(instance); var cost = DateTime.Now - tic; Console.WriteLine($"{tag} after in {cost.TotalMilliseconds}ms"); } // 带返回值方法的通用包裹,所有类型通用 public static TResult RunWithWrap<T, TResult>(this T instance, Func<T, TResult> func, string tag) { Console.WriteLine($"{tag} before"); var tic = DateTime.Now; var res = func(instance); var cost = DateTime.Now - tic; Console.WriteLine($"{tag} after in {cost.TotalMilliseconds}ms"); return res; } }
使用方式也很简单:
// 创建实例自动初始化 var foo = ObjectFactory.Create<FooService>(); // 调用方法自动包裹 foo.RunWithWrap(f => f.Foo(), "spam");
这个方案没有任何额外依赖,逻辑完全透明可控,缺点是需要手动通过工厂创建实例、调用包裹方法,做不到完全无侵入。
注意:不推荐使用需要修改构建流程的静态织入类AOP框架,这类工具大多需要安装IDE插件、定制编译步骤,出现问题排查成本很高。
内容的提问来源于stack exchange,提问作者Dario

