2026-05-15
C#
0

目录

🎯 开发痛点:传统数据处理的困境
💡 解决方案:委托驱动的优雅架构
🚩 设计流程
🛠️ 代码实战:构建企业级数据管道
📋 核心数据模型
🔧 委托驱动的处理管道
🚀 实际应用示例
🌟 异步版本:面向现代应用
⚠️ 实战避坑指南
🔸 委托null判断
🔸 异常处理策略
🔸 性能优化技巧
🎯 工业级应用场景
📈 性能与维护性对比
💝 总结:三个核心要点

作为C#开发者,你是否经常被复杂的回调逻辑搞得头疼?是否在写数据处理管道时觉得代码冗长难维护?今天我要和你分享一个让代码瞬间变优雅的秘密武器:C#内置委托Action和Func

通过一个真实的数据处理项目案例,我将向你展示如何用寥寥几行委托代码,就能构建出可维护、可扩展的企业级数据处理管道。相信我,掌握这个技能后,你的代码质量将有质的飞跃!

🎯 开发痛点:传统数据处理的困境

在日常开发中,我们经常遇到这样的场景:

  • 从CSV文件读取用户数据
  • 按条件过滤无效记录
  • 对数据进行清洗和转换
  • 最后写入数据库或发送到消息队列

传统写法往往是:

c#
// 传统方式:代码冗长、耦合严重 public void ProcessUserData() { var users = ReadCsvFile(); foreach(var user in users) { if(user.Age >= 18) // 硬编码过滤条件 { user.Email = CleanEmail(user.Email); // 硬编码转换逻辑 WriteToDatabase(user); // 硬编码输出方式 } } }

这样的代码存在致命问题:

  • 耦合度高:过滤、转换、输出逻辑混在一起
  • 扩展性差:新增需求需要修改主流程
  • 复用性低:无法灵活组合不同的处理步骤

💡 解决方案:委托驱动的优雅架构

Action和Func委托就是为了解决这类问题而生:

  • Action<T>:无返回值的委托,完美适配"执行某个动作"的场景
  • Func<T, TResult>:有返回值的委托,适配"数据转换"和"条件判断"的场景

🚩 设计流程

image.png

🛠️ 代码实战:构建企业级数据管道

📋 核心数据模型

c#
public class UserRecord { public int Id { get; set; } public string Name { get; set; } = ""; public string Email { get; set; } = ""; public int Age { get; set; } public override string ToString() => $"Id={Id}, Name={Name}, Email={Email}, Age={Age}"; }

🔧 委托驱动的处理管道

c#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AppDataPipeline { public class DataPipeline { public static void Process( Func<IEnumerable<UserRecord>> loader, // 数据加载器 Func<UserRecord, bool>? filter, // 过滤条件 Func<UserRecord, UserRecord>? transform, // 数据转换 Action<UserRecord> action, // 最终动作 Action<string>? logger = null) // 日志记录 { logger?.Invoke("Pipeline started."); var records = loader() ?? Enumerable.Empty<UserRecord>(); var total = 0; var passed = 0; foreach (var rec in records) { total++; logger?.Invoke($"Loaded: {rec}"); // 应用过滤器 if (filter != null && !filter(rec)) { logger?.Invoke($"Filtered out: {rec}"); continue; } passed++; // 应用转换器 var newRec = transform != null ? transform(rec) : rec; logger?.Invoke($"Transformed: {newRec}"); // 执行最终动作 action(newRec); } logger?.Invoke($"Pipeline finished. Total={total}, Passed={passed}"); } } }

🚀 实际应用示例

c#
using CsvHelper; using System.Globalization; namespace AppDataPipeline { internal class Program { static async Task Main(string[] args) { var csvPath = "users_example.csv"; // 数据加载器:使用 Func 委托 Func<IEnumerable<UserRecord>> loader = () => ReadUsersFromCsv(csvPath); // 过滤器:只保留成年用户 Func<UserRecord, bool> filter = u => u.Age >= 18; // 转换器:数据清洗和标准化 Func<UserRecord, UserRecord> transform = u => { var email = u.Email ?? ""; // 修复常见邮箱格式错误 if (email.Contains("[at]")) email = email.Replace("[at]", "@"); if (string.IsNullOrWhiteSpace(email)) email = $"{u.Name.ToLowerInvariant()}@example.local"; // 标准化姓名格式 var name = u.Name?.Trim() ?? ""; name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name.ToLowerInvariant()); return new UserRecord { Id = u.Id, Name = name, Email = email, Age = u.Age }; }; // 输出动作:写入数据库 Action<UserRecord> writeDb = u => Console.WriteLine($"[DB] Writing user: {u}"); // 日志记录 Action<string> logger = msg => Console.WriteLine($"[LOG] {DateTime.Now:HH:mm:ss} - {msg}"); // 一行代码搞定整个管道! DataPipeline.Process(loader, filter, transform, writeDb, logger); } private static IEnumerable<UserRecord> ReadUsersFromCsv(string path) { using var reader = new StreamReader(path); using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); var records = csv.GetRecords<UserRecord>().ToList(); return records; } } }

image.png

有段时间我非常喜欢这么干

🌟 异步版本:面向现代应用

c#
public static async Task ProcessAsync( Func<Task<IEnumerable<UserRecord>>> loaderAsync, Func<UserRecord, bool>? filter, Func<UserRecord, UserRecord>? transform, Func<UserRecord, Task>? actionAsync, Action<string>? logger = null) { logger?.Invoke("Async pipeline started."); var records = await loaderAsync() ?? Enumerable.Empty<UserRecord>(); foreach (var rec in records) { // ... 过滤和转换逻辑同上 if (actionAsync != null) await actionAsync(newRec); } } // 使用异步版本 Func<Task<IEnumerable<UserRecord>>> loaderAsync = async () => { await Task.Delay(100); // 模拟异步I/O return CsvHelper.ReadUsersFromCsv(csvPath); }; Func<UserRecord, Task> sendToQueueAsync = async (user) => { await Task.Delay(50); // 模拟异步发送到消息队列 Console.WriteLine($"[Queue] Sent user: {user}"); }; await DataPipeline.ProcessAsync(loaderAsync, filter, transform, sendToQueueAsync, logger);

⚠️ 实战避坑指南

🔸 委托null判断

c#
// ❌ 错误:直接调用可能为null的委托 filter(record); // ✅ 正确:先判断null再调用 if (filter != null && !filter(record)) continue;

🔸 异常处理策略

c#
public static void ProcessSafe( Func<IEnumerable<UserRecord>> loader, Func<UserRecord, bool>? filter, Func<UserRecord, UserRecord>? transform, Action<UserRecord> action, Action<Exception>? errorHandler = null) { try { // 处理逻辑 } catch (Exception ex) { errorHandler?.Invoke(ex); } }

🔸 性能优化技巧

c#
// 使用 yield return 提升内存效率 public static IEnumerable<UserRecord> ReadUsersFromCsv(string filePath) { if (!File.Exists(filePath)) yield break; foreach (var line in File.ReadLines(filePath)) { // 逐行处理,避免一次性加载大文件 yield return ParseUserRecord(line); } }

🎯 工业级应用场景

  1. ETL数据处理管道:Extract-Transform-Load流程的完美实现
  2. 微服务事件处理:灵活的消息处理和路由机制
  3. 批量数据迁移:大数据量场景下的分批处理
  4. 实时数据清洗:流式数据处理中的转换逻辑
  5. 配置驱动的业务规则:运行时动态加载处理规则

📈 性能与维护性对比

维度传统方式委托方式
代码复用❌ 低✅ 高
单元测试❌ 困难✅ 简单
扩展性❌ 修改主逻辑✅ 组合委托
可读性❌ 逻辑混乱✅ 职责清晰
性能🔸 一般✅ 优秀

💝 总结:三个核心要点

通过这个实战案例,我们见识了C#内置委托的强大威力:

🔥 架构优势:委托让我们轻松实现了"组合优于继承"的设计原则,每个处理步骤都是独立的、可复用的函数式组件。

⚡ 开发效率:一个通用的DataPipeline.Process方法,配合不同的委托组合,就能应对各种数据处理场景,大大减少了重复代码。

🛡️ 质量保证:每个委托都可以独立进行单元测试,大幅提升了代码的可测试性和可维护性。


你在项目中是否遇到过类似的数据处理场景?或者对委托的其他应用场景有什么想法?

欢迎在评论区分享你的经验和问题,让我们一起探讨C#开发的更多可能性!

如果这篇文章对你有帮助,请转发给更多需要提升代码质量的同行,让优雅编程成为我们共同的追求!

#C#开发 #编程技巧 #软件架构 #委托模式 #数据处理

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!