作为C#开发者,你是否经常被复杂的回调逻辑搞得头疼?是否在写数据处理管道时觉得代码冗长难维护?今天我要和你分享一个让代码瞬间变优雅的秘密武器:C#内置委托Action和Func。
通过一个真实的数据处理项目案例,我将向你展示如何用寥寥几行委托代码,就能构建出可维护、可扩展的企业级数据处理管道。相信我,掌握这个技能后,你的代码质量将有质的飞跃!
在日常开发中,我们经常遇到这样的场景:
传统写法往往是:
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委托就是为了解决这类问题而生:

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;
}
}
}

有段时间我非常喜欢这么干
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);
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);
}
}
| 维度 | 传统方式 | 委托方式 |
|---|---|---|
| 代码复用 | ❌ 低 | ✅ 高 |
| 单元测试 | ❌ 困难 | ✅ 简单 |
| 扩展性 | ❌ 修改主逻辑 | ✅ 组合委托 |
| 可读性 | ❌ 逻辑混乱 | ✅ 职责清晰 |
| 性能 | 🔸 一般 | ✅ 优秀 |
通过这个实战案例,我们见识了C#内置委托的强大威力:
🔥 架构优势:委托让我们轻松实现了"组合优于继承"的设计原则,每个处理步骤都是独立的、可复用的函数式组件。
⚡ 开发效率:一个通用的DataPipeline.Process方法,配合不同的委托组合,就能应对各种数据处理场景,大大减少了重复代码。
🛡️ 质量保证:每个委托都可以独立进行单元测试,大幅提升了代码的可测试性和可维护性。
你在项目中是否遇到过类似的数据处理场景?或者对委托的其他应用场景有什么想法?
欢迎在评论区分享你的经验和问题,让我们一起探讨C#开发的更多可能性!
如果这篇文章对你有帮助,请转发给更多需要提升代码质量的同行,让优雅编程成为我们共同的追求!
#C#开发 #编程技巧 #软件架构 #委托模式 #数据处理
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!