是否可以在C#中创建这样的Builder?

问题描述 投票:0回答:1
var pipeline = new PipelineBuilder<string>
    .AddInitial(n => n*12)
    .AddStep(n => n.ToString())
    .Build();

执行 `pipeline(2)' 应返回“24”。

此外,构建器中的类型参数

<string>
指示结果的类型(最终步骤的返回类型)。

这可能吗?我已经与 C# 的类型系统进行了斗争,但我已经完成了。

我离它越近的是这段代码:

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
)。

如果类型不匹配,我得到的只是运行时异常。我什至不知道它们在执行

Build
方法时是否匹配,因为函数是
Func<dynamic, dynamic>

c# .net design-patterns dynamic type-safety
1个回答
0
投票

可以通过将 lambda 链接在一起来做一些非常接近你想要的事情,如下所示:

public class PipelineBuilder<TFinal>
{
    public PipelineBuilder<TInitial, TCurrent, TFinal> AddInitial<TInitial, TCurrent>(Func<TInitial, TCurrent> step)
        => new PipelineBuilder<TInitial, TCurrent, TFinal> { Step  = step };
}

public class PipelineBuilder<TInitial, TCurrent, TFinal>
{
    // Represents an intermediate step in the pipeline.
    internal Func<TInitial, TCurrent> Step { get; init; }

    public PipelineBuilder<TInitial, TNext, TFinal> AddStep<TNext>(Func<TCurrent, TNext> step)
        => new PipelineBuilder<TInitial, TNext, TFinal> {  Step = (c) => step(Step(c)) };
}

public static class PipelineBuilderExtensions
{
    public static Func<TInitial, TFinal> Build<TInitial, TFinal>(this PipelineBuilder<TInitial, TFinal, TFinal> current) 
        => current.Step;
}

然后按如下方式使用:

var pipeline = new PipelineBuilder<string>()
    .AddInitial((int n) => n*12) // (int n) is necessary to declare the initial type
    .AddStep(n => n.ToString())
    .Build();

string result = pipeline(2); // Here I declare result as string to demonstrate that the returned value is of the expected type.  Normally one would use var/

Console.WriteLine(result); // Prints 24.

演示小提琴#1 这里

备注:

  • 在您的初始设计中,编译器无法推断输入参数的类型

    n

    var pipeline = new PipelineBuilder<string>
        .AddInitial(n => n*12)  // Is n an int, a TimeSpan, a string, or what?
    

    向 lambda 参数添加类型

    (int n)
    可以解决歧义。

  • 如果当前步骤结果类型不等于最终步骤结果类型,则将找不到扩展方法

    .Build()
    ,并且您将收到编译器错误:

    'PipelineBuilder<int, int, string>' does not contain a definition for 'Build' and no accessible extension method 'Build' accepting a first argument of type 'PipelineBuilder<int, int, string>' could be found (are you missing a using directive or an assembly reference?)
    Argument 1: cannot convert from 'int' to 'TInitial'
    

    该错误并不是特别具有描述性,但它仍然是一个正确的编译器错误,而不是运行时异常。

    演示小提琴 #2 这里

  • 综上所述,我不推荐这种设计。当最终类型也将由最终

    AddStep()
    调用定义时,在开始时定义最终结果类型感觉很尴尬且与 LINQ 编程风格不一致。从初始声明中消除
    TFinal
    使事情变得更加简单:

    public class PipelineBuilder
    {
        public PipelineBuilder<TInitial, TCurrent> AddInitial<TInitial, TCurrent>(Func<TInitial, TCurrent> step)
            => new PipelineBuilder<TInitial, TCurrent> { Step  = step };
    }
    
    public class PipelineBuilder<TInitial, TCurrent>
    {
        // Represents an intermediate step in the pipeline.
        internal Func<TInitial, TCurrent> Step { get; init; }
    
        public PipelineBuilder<TInitial, TNext> AddStep<TNext>(Func<TCurrent, TNext> step)
            => new PipelineBuilder<TInitial, TNext> {  Step = (c) => step(Step(c)) };
    
        public Func<TInitial, TCurrent> Build() => Step;
    }
    

    演示小提琴 #3 这里

© www.soinside.com 2019 - 2024. All rights reserved.