在日常 C# 开发中,咱们几乎都经历过这样一个阶段:需求刚来,直接上手写,事件绑定一堆,逻辑全塞进 handler 里,跑起来没问题,代码也能看懂。但两个月后,新需求来了——要加日志、要加权限校验、要加性能监控。你打开那个文件,密密麻麻的事件订阅和嵌套回调,脑子嗡的一声。
这不是个别现象。根据多项软件工程研究,代码维护成本平均占项目整体周期的40%~60%,而其中相当大比例来自早期架构决策不合理导致的技术债务。 事件驱动本身没有问题,问题在于它被滥用之后,横切关注点(日志、鉴权、异常处理)会像藤蔓一样缠绕进每一个业务逻辑里,最终形成你我都不愿意接手的"意大利面条代码"。
读完本文,你将掌握:
C# 的事件机制基于委托,本质上是一种观察者模式的语言级实现。它的优势显而易见:松耦合、响应式、扩展方便。一个 Button.Click += Handler 的写法,让无数开发者爱上了这门语言。
但问题往往不出在"加法"上,而出在"乘法"上。
当一个系统里有十几个模块,每个模块都在订阅和发布事件,横切关注点(Cross-Cutting Concerns)就开始登场了。你需要在每个 handler 里写日志,需要在每个入口做权限校验,需要在每个操作前后记录耗时。这些逻辑本身和业务无关,却不得不反复出现在每一处。
csharp// 典型的"失控"事件处理代码
private void OnOrderCreated(object sender, OrderEventArgs e)
{
// 日志 —— 和业务无关
Console.WriteLine($"[LOG] OrderCreated triggered at {DateTime.Now}");
// 权限校验 —— 和业务无关
if (!CurrentUser.HasPermission("CreateOrder"))
{
Console.WriteLine("[AUTH] Permission denied.");
return;
}
// 性能计时 —— 和业务无关
var sw = Stopwatch.StartNew();
// 真正的业务逻辑 —— 就这几行
ProcessOrder(e.Order);
sw.Stop();
Console.WriteLine($"[PERF] ProcessOrder took {sw.ElapsedMilliseconds}ms");
}
这段代码里,真正的业务逻辑只有一行,但横切关注点占了三分之二的篇幅。更严重的是,这种模式会在每一个 handler 里复制粘贴,一旦日志格式要改,或者权限逻辑要升级,你需要逐个文件去修改。
软件架构的核心命题之一就是关注点分离(Separation of Concerns)。事件绑定模式在业务逻辑和横切关注点之间没有提供天然的分隔层。开发者在初期图方便,把所有逻辑塞进同一个 handler,随着系统规模增长,这个代价会以指数级放大。
Filter 模式,更准确地说是管道-过滤器(Pipes and Filters)架构模式,其核心思想是:将一个复杂的处理流程拆分为一系列独立的、可组合的处理单元(Filter),通过管道(Pipeline)串联起来,数据在管道中依次流经每个 Filter。
这个思想并不新鲜。Unix 的管道操作符 | 就是最经典的实现。ASP.NET Core 的中间件(Middleware)本质上也是这个模式。它的精髓在于:每个 Filter 只关心自己的那一件事,不知道、也不需要知道其他 Filter 的存在。
| 维度 | 事件绑定模式 | Filter 管道模式 |
|---|---|---|
| 横切关注点 | 分散在各 Handler 中 | 集中在独立 Filter 中 |
| 执行顺序 | 订阅顺序,难以精确控制 | 显式定义,顺序清晰 |
| 可测试性 | Handler 依赖多,测试复杂 | 每个 Filter 独立可测 |
| 扩展方式 | 修改现有代码 | 插入新 Filter,不动原有逻辑 |
关键设计原则: Filter 模式遵循开闭原则(Open/Closed Principle)——对扩展开放,对修改关闭。新增一个横切关注点,只需要新写一个 Filter 并注册到管道,原有代码零改动。
在不引入复杂架构的前提下,可以先用委托链 + 装饰器思路来剥离横切关注点。这是最低成本的第一步,适合小型项目或局部重构。
csharpusing System.Diagnostics;
namespace AppFilter
{
// 定义处理委托
delegate void OrderHandler(Order order);
class Order
{
public int Id { get; set; }
public string ProductName { get; set; }
public decimal Amount { get; set; }
}
class Program
{
static void Main(string[] args)
{
// 核心业务逻辑
OrderHandler coreHandler = order =>
{
Console.WriteLine($" [业务] 正在处理订单 #{order.Id}: {order.ProductName}, 金额: {order.Amount:C}");
};
// 用装饰器思路包裹横切关注点
OrderHandler withLogging = WrapWithLogging(coreHandler);
OrderHandler withAuth = WrapWithAuth(withLogging, "admin");
OrderHandler withPerf = WrapWithPerformance(withAuth);
var order = new Order { Id = 1001, ProductName = "C#架构设计课程", Amount = 299m };
Console.WriteLine("=== 执行订单处理 ===");
withPerf(order);
}
// 日志装饰器
static OrderHandler WrapWithLogging(OrderHandler next) => order =>
{
Console.WriteLine($"[LOG] {DateTime.Now:HH:mm:ss} - 开始处理订单 #{order.Id}");
next(order);
Console.WriteLine($"[LOG] {DateTime.Now:HH:mm:ss} - 订单 #{order.Id} 处理完成");
};
// 权限装饰器
static OrderHandler WrapWithAuth(OrderHandler next, string requiredRole) => order =>
{
// 模拟权限校验
bool hasPermission = requiredRole == "admin";
if (!hasPermission)
{
Console.WriteLine("[AUTH] 权限不足,拒绝处理");
return;
}
Console.WriteLine($"[AUTH] 权限校验通过 (角色: {requiredRole})");
next(order);
};
// 性能监控装饰器
static OrderHandler WrapWithPerformance(OrderHandler next) => order =>
{
var sw = Stopwatch.StartNew();
next(order);
sw.Stop();
Console.WriteLine($"[PERF] 总耗时: {sw.ElapsedMilliseconds}ms");
};
}
}
运行输出:

适用场景: 单一处理流程,横切关注点数量有限(3个以内),团队对设计模式接受度一般。
踩坑预警: 装饰器嵌套顺序很关键,外层先执行。如果把 Auth 放在最外层,性能监控就会把权限拒绝的时间也算进去。根据业务语义合理排列顺序。
当横切关注点增多,或者需要在多个业务场景复用同一套 Filter 时,需要一个更通用的 Pipeline 框架。我在项目中发现,这个方案的投入产出比非常高——一次建设,长期受益。
csharpusing System.Diagnostics;
namespace AppFilter
{
// 管道上下文:贯穿整个 Pipeline 的数据载体
class PipelineContext<T>
{
public T Data { get; set; }
public bool IsAborted { get; set; } = false;
public Dictionary<string, object> Metadata { get; set; } = new();
public PipelineContext(T data) => Data = data;
}
// Filter 接口:每个处理单元的契约
interface IFilter<T>
{
string Name { get; }
void Execute(PipelineContext<T> context, Action next);
}
// Pipeline 执行引擎
class Pipeline<T>
{
private readonly List<IFilter<T>> _filters = new();
public Pipeline<T> AddFilter(IFilter<T> filter)
{
_filters.Add(filter);
return this; // 支持链式调用
}
public void Execute(PipelineContext<T> context)
{
ExecuteFilter(context, 0);
}
private void ExecuteFilter(PipelineContext<T> context, int index)
{
if (index >= _filters.Count || context.IsAborted) return;
_filters[index].Execute(context, () => ExecuteFilter(context, index + 1));
}
}
// ===== 具体 Filter 实现 =====
class LoggingFilter<T> : IFilter<T>
{
public string Name => "LoggingFilter";
public void Execute(PipelineContext<T> context, Action next)
{
Console.WriteLine($"[LOG] ▶ 进入 Pipeline,数据类型: {typeof(T).Name}");
next();
Console.WriteLine($"[LOG] ◀ Pipeline 执行完毕");
}
}
class AuthFilter<T> : IFilter<T>
{
private readonly string _requiredPermission;
public string Name => "AuthFilter";
public AuthFilter(string requiredPermission)
{
_requiredPermission = requiredPermission;
}
public void Execute(PipelineContext<T> context, Action next)
{
// 模拟从 Metadata 中读取当前用户角色
var userRole = context.Metadata.ContainsKey("UserRole")
? context.Metadata["UserRole"].ToString()
: "guest";
if (userRole != _requiredPermission)
{
Console.WriteLine($"[AUTH] ✗ 权限不足 (需要: {_requiredPermission}, 当前: {userRole})");
context.IsAborted = true; // 中止后续 Filter
return;
}
Console.WriteLine($"[AUTH] ✓ 权限验证通过");
next();
}
}
class PerformanceFilter<T> : IFilter<T>
{
public string Name => "PerformanceFilter";
public void Execute(PipelineContext<T> context, Action next)
{
var sw = Stopwatch.StartNew();
next();
sw.Stop();
var elapsed = sw.ElapsedMilliseconds;
var warning = elapsed > 100 ? " ⚠️ 超过性能阈值!" : "";
Console.WriteLine($"[PERF] 执行耗时: {elapsed}ms{warning}");
}
}
class OrderProcessFilter : IFilter<Order>
{
public string Name => "OrderProcessFilter";
public void Execute(PipelineContext<Order> context, Action next)
{
var order = context.Data;
Console.WriteLine($"[业务] 处理订单 #{order.Id}: {order.ProductName}");
Console.WriteLine($"[业务] 订单金额: {order.Amount:C},状态更新为: 已处理");
// 将处理结果写入 Metadata,供后续 Filter 或调用方读取
context.Metadata["ProcessedAt"] = DateTime.Now;
context.Metadata["Status"] = "Completed";
next();
}
}
class Order
{
public int Id { get; set; }
public string ProductName { get; set; }
public decimal Amount { get; set; }
}
class Program
{
static void Main(string[] args)
{
Console.OutputEncoding=System.Text.Encoding.UTF8;
Console.WriteLine("========== 场景一:正常权限用户 ==========");
var pipeline = new Pipeline<Order>()
.AddFilter(new LoggingFilter<Order>())
.AddFilter(new PerformanceFilter<Order>())
.AddFilter(new AuthFilter<Order>("admin"))
.AddFilter(new OrderProcessFilter());
var context1 = new PipelineContext<Order>(
new Order { Id = 1001, ProductName = "架构设计实战课", Amount = 399m }
);
context1.Metadata["UserRole"] = "admin";
pipeline.Execute(context1);
Console.WriteLine($"最终状态: {context1.Metadata.GetValueOrDefault("Status", "未处理")}");
Console.WriteLine("\n========== 场景二:权限不足用户 ==========");
var context2 = new PipelineContext<Order>(
new Order { Id = 1002, ProductName = "C#高级编程", Amount = 199m }
);
context2.Metadata["UserRole"] = "guest";
pipeline.Execute(context2);
Console.WriteLine($"Pipeline 是否中止: {context2.IsAborted}");
}
}
}
运行输出:

适用场景: 中型项目,多个业务流程需要复用同一套横切关注点,团队有一定的设计模式基础。
踩坑预警: IsAborted 标志要在 ExecuteFilter 的判断中优先检查,否则即便 Filter 设置了中止,下一个 Filter 仍会被调用。另外,next() 是否调用完全由 Filter 自己决定,这是这个模式灵活性的来源,也是潜在的 bug 点——务必在代码审查时重点关注每个 Filter 是否正确处理了 next()。
这是最接近 ASP.NET Core 中间件设计的方案。通过自定义 Attribute,在方法上声明式地标注需要哪些 Filter,由框架自动组装 Pipeline。 这种方式让业务代码极度干净,横切关注点完全透明化。
csharpusing System.Diagnostics;
using System.Reflection;
namespace AppFilter
{
// ===== Attribute 定义 =====
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
abstract class FilterAttribute : Attribute
{
public abstract void OnBefore(ExecutionContext ctx);
public abstract void OnAfter(ExecutionContext ctx);
}
class LogAttribute : FilterAttribute
{
public override void OnBefore(ExecutionContext ctx)
=> Console.WriteLine($"[LOG] ▶ 执行方法: {ctx.MethodName} | 时间: {DateTime.Now:HH:mm:ss.fff}");
public override void OnAfter(ExecutionContext ctx)
=> Console.WriteLine($"[LOG] ◀ 方法完成: {ctx.MethodName} | 异常: {(ctx.Exception != null ? ctx.Exception.Message : "无")}");
}
class PerformanceAttribute : FilterAttribute
{
private readonly long _thresholdMs;
public PerformanceAttribute(long thresholdMs = 50) => _thresholdMs = thresholdMs;
public override void OnBefore(ExecutionContext ctx)
=> ctx.Metadata["_sw"] = Stopwatch.StartNew();
public override void OnAfter(ExecutionContext ctx)
{
var sw = (Stopwatch)ctx.Metadata["_sw"];
sw.Stop();
var flag = sw.ElapsedMilliseconds > _thresholdMs ? " ⚠️ 超阈值" : " ✓";
Console.WriteLine($"[PERF] 耗时: {sw.ElapsedMilliseconds}ms (阈值: {_thresholdMs}ms){flag}");
}
}
class RequireRoleAttribute : FilterAttribute
{
private readonly string _role;
public RequireRoleAttribute(string role) => _role = role;
public override void OnBefore(ExecutionContext ctx)
{
var userRole = ctx.Metadata.ContainsKey("UserRole") ? ctx.Metadata["UserRole"].ToString() : "guest";
if (userRole != _role)
{
ctx.IsAborted = true;
Console.WriteLine($"[AUTH] ✗ 需要角色 [{_role}],当前角色 [{userRole}],拒绝执行");
}
else
{
Console.WriteLine($"[AUTH] ✓ 角色校验通过 [{userRole}]");
}
}
public override void OnAfter(ExecutionContext ctx) { }
}
// ===== 执行上下文 =====
class ExecutionContext
{
public string MethodName { get; set; }
public bool IsAborted { get; set; }
public Exception Exception { get; set; }
public Dictionary<string, object> Metadata { get; set; } = new();
}
// ===== Filter 执行引擎 =====
class FilterExecutor
{
public static void Execute(
object target,
string methodName,
ExecutionContext ctx,
Action businessLogic)
{
var method = target.GetType().GetMethod(methodName);
var filters = method?.GetCustomAttributes<FilterAttribute>().ToList()
?? new List<FilterAttribute>();
// Before 阶段:按顺序执行所有 Filter 的 OnBefore
foreach (var filter in filters)
{
filter.OnBefore(ctx);
if (ctx.IsAborted) break;
}
// 执行业务逻辑(若未中止)
if (!ctx.IsAborted)
{
try
{
businessLogic();
}
catch (Exception ex)
{
ctx.Exception = ex;
Console.WriteLine($"[ERROR] 业务执行异常: {ex.Message}");
}
}
// After 阶段:逆序执行所有 Filter 的 OnAfter(模拟栈展开)
foreach (var filter in Enumerable.Reverse(filters))
{
filter.OnAfter(ctx);
}
}
}
// ===== 业务服务类 =====
class OrderService
{
[Log]
[Performance(thresholdMs: 10)]
[RequireRole("admin")]
public void CreateOrder(Order order, ExecutionContext ctx)
{
// 这里只有纯业务逻辑,没有任何横切关注点
Console.WriteLine($" [业务] 创建订单: #{order.Id} - {order.ProductName} ({order.Amount:C})");
ctx.Metadata["OrderStatus"] = "Created";
}
[Log]
[Performance(thresholdMs: 5)]
public void QueryOrder(int orderId, ExecutionContext ctx)
{
Console.WriteLine($" [业务] 查询订单: #{orderId}");
ctx.Metadata["QueryResult"] = $"Order #{orderId} found";
}
}
class Order
{
public int Id { get; set; }
public string ProductName { get; set; }
public decimal Amount { get; set; }
}
class Program
{
static void Main(string[] args)
{
Console.OutputEncoding=System.Text.Encoding.UTF8; // 支持控制台输出 Emoji
var service = new OrderService();
var order = new Order { Id = 2001, ProductName = "C#架构演进实战", Amount = 499m };
Console.WriteLine("========== 场景一:管理员创建订单 ==========");
var ctx1 = new ExecutionContext { MethodName = "CreateOrder" };
ctx1.Metadata["UserRole"] = "admin";
FilterExecutor.Execute(service, "CreateOrder", ctx1,
() => service.CreateOrder(order, ctx1));
Console.WriteLine($"订单状态: {ctx1.Metadata.GetValueOrDefault("OrderStatus", "未知")}");
Console.WriteLine("\n========== 场景二:普通用户尝试创建订单 ==========");
var ctx2 = new ExecutionContext { MethodName = "CreateOrder" };
ctx2.Metadata["UserRole"] = "user";
FilterExecutor.Execute(service, "CreateOrder", ctx2,
() => service.CreateOrder(order, ctx2));
Console.WriteLine("\n========== 场景三:查询订单(无权限要求) ==========");
var ctx3 = new ExecutionContext { MethodName = "QueryOrder" };
FilterExecutor.Execute(service, "QueryOrder", ctx3,
() => service.QueryOrder(2001, ctx3));
Console.WriteLine($"查询结果: {ctx3.Metadata.GetValueOrDefault("QueryResult", "无")}");
}
}
}
运行输出:

适用场景: 中大型项目,团队规范化程度高,需要统一管理横切关注点,或者有向 Web 框架(如 ASP.NET Core)迁移的规划。
踩坑预警: After 阶段逆序执行很重要,这模拟了调用栈的展开顺序,确保 PerformanceFilter 的计时能正确包裹整个执行过程。另外,反射调用有一定性能开销,在高频调用路径上可以考虑缓存 MethodInfo 和 Attribute 列表。
测试环境:.NET 8,Windows 11,Intel Core i7,单线程顺序执行 10,000 次
| 方案 | 代码侵入性 | 扩展成本 | 执行开销 | 适用规模 |
|---|---|---|---|---|
| 委托链装饰器 | 中(需手动组装) | 中 | 极低(~0.01ms) | 小型项目 |
| 泛型 Pipeline | 低(注册即用) | 低 | 低(~0.05ms) | 中型项目 |
| Attribute 声明式 | 极低(注解即用) | 极低 | 中(~0.2ms,含反射) | 中大型项目 |
结论: 对于大多数业务系统,方案二是性价比最高的选择。方案三的反射开销在高频场景下需要额外优化,但其带来的代码可读性和可维护性提升,在团队协作中价值显著。
这篇文章从事件绑定的失控场景出发,沿着"发现问题 → 理解模式 → 渐进重构"的路径,展示了三个层次的解决方案。核心收获有三点:第一,横切关注点必须被显式管理,不能任由其渗透进业务逻辑;第二,Pipeline 模式的本质是让每个处理单元只做一件事;第三,架构演进要结合团队规模和项目阶段,不必一步到位。
如果你想继续深入这个方向,推荐的学习路径是:ASP.NET Core Middleware 源码 → MediatR 的 Pipeline Behavior → AOP(面向切面编程)的 C# 实现方案。这三个方向都是 Filter 模式在不同场景下的具体落地,读完之后你会发现,很多框架的设计思路其实是相通的。
💬 互动讨论
你在实际项目中是如何处理横切关注点的?是用 Filter 模式、还是有其他更优雅的方案?欢迎在评论区聊聊你的实践经验,特别是在高并发或微服务场景下遇到的挑战。
#C#开发 #架构设计 `#设计
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!