能否在C#中实现指定最终返回类型的PipelineBuilder构建器?
实现类型安全的C# PipelineBuilder构建器
需求描述
想要在C#中实现如下的PipelineBuilder构建器:
var pipeline = new PipelineBuilder<string>() .AddInitial(n => n * 12) .AddStep(n => n.ToString()) .Build();
执行pipeline(2)应返回"24",其中构建器的泛型参数<string>指定最终步骤的返回类型。
现有实现的问题
尝试了如下代码:
public class PipelineBuilder<TInitial, TResult> { private List<Func<dynamic, dynamic>> functions = new(); public PipelineBuilder<TInitial, TOutput, TResult> AddInitial<TOutput>(Func<TInitial, TOutput> step) { functions.Add(o => step(o)); return new PipelineBuilder<TInitial, TOutput, TResult>(functions); } public PipelineBuilder<TInput, TOutput, TResult> AddStep<TInput, TOutput>(Func<TOutput, TInput> step) { functions.Add(a => step(a)); return new PipelineBuilder<TInput, TOutput, TResult>(functions); } } public class PipelineBuilder<TInput, TOutput, TResult> { private List<Func<dynamic, dynamic>> functions; public PipelineBuilder(List<Func<dynamic, dynamic>> functions) { this.functions = functions; } public PipelineBuilder<TInput, TNewOutput, TResult> AddStep<TNewOutput>(Func<TOutput, TNewOutput> step) { functions.Add(a => step(a)); return new PipelineBuilder<TInput, TNewOutput, TResult>(functions); } public Func<TInput, TResult> Build() { return input => { dynamic result = input; foreach (var step in functions) { result = step(result); } return (TResult)(object)result; }; } }
但该方案存在明显缺陷:
- 无编译期类型校验:无法确保最终步骤的输出类型与构建器指定的
TResult匹配,类型不匹配时只会在运行时抛出异常 - 动态类型风险:使用
Func<dynamic, dynamic>导致整个管道流程失去编译时类型检查,容易引入隐性错误
类型安全的解决方案
通过分层泛型类跟踪管道每一步的输入、输出类型,在编译阶段就校验最终输出与TResult的兼容性,彻底避免运行时类型错误。
实现代码
// 入口构建器:仅指定最终返回类型 public class PipelineBuilder<TResult> { // 添加初始步骤,确定管道的输入类型和第一步输出类型 public PipelineBuilder<TInput, TFirstOutput, TResult> AddInitial<TInput, TFirstOutput>(Func<TInput, TFirstOutput> initialStep) { return new PipelineBuilder<TInput, TFirstOutput, TResult>(initialStep); } } // 中间构建器:跟踪当前输入、当前输出、最终返回类型 public class PipelineBuilder<TInput, TCurrentOutput, TResult> { private readonly Func<TInput, TCurrentOutput> _pipeline; internal PipelineBuilder(Func<TInput, TCurrentOutput> pipeline) { _pipeline = pipeline; } // 添加后续步骤,链式更新当前输出类型 public PipelineBuilder<TInput, TNewOutput, TResult> AddStep<TNewOutput>(Func<TCurrentOutput, TNewOutput> step) { Func<TInput, TNewOutput> newPipeline = input => step(_pipeline(input)); return new PipelineBuilder<TInput, TNewOutput, TResult>(newPipeline); } // 构建最终管道:编译期校验当前输出类型是否可转换为TResult public Func<TInput, TResult> Build() { // 若TCurrentOutput无法转换为TResult,编译时直接报错 return input => (TResult)(object)_pipeline(input); } }
使用示例
// 创建符合需求的管道 var pipeline = new PipelineBuilder<string>() .AddInitial<int, int>(n => n * 12) .AddStep(n => n.ToString()) .Build(); // 执行管道,返回"24" string result = pipeline(2); Console.WriteLine(result); // 输出:24
类型安全校验示例
如果最后一步返回类型与TResult不匹配,编译阶段会直接报错:
// 错误示例:最后一步返回int,与指定的string类型不兼容 var pipeline = new PipelineBuilder<string>() .AddInitial<int, int>(n => n * 12) .AddStep(n => n + 1) // 返回int类型 .Build(); // 编译报错:无法将int转换为string
优化类型推断
可以通过显式指定输入类型让编译器自动推断输出类型,简化代码:
var pipeline = new PipelineBuilder<string>() .AddInitial((int n) => n * 12) // 编译器自动推断输出为int .AddStep(n => n.ToString()) .Build();
内容的提问来源于stack exchange,提问作者SuperJMN
相关产品推荐
相关产品推荐

