C# 使用Func的责任链

C# 使用Func的责任链,c#,chain-of-responsibility,C#,Chain Of Responsibility,我正在使用System.Func创建一个责任链管道,管道中的每个函数都包含对下一个函数的引用 在构建管道时,我无法通过引用传递内部函数,因为重新分配管道函数会引发StackOverflowException,例如: Func<string, Func<string, string>, string> handler1 = (s, next) => { s = s.ToUpper(); return next.Invoke(s); }; Func&l

我正在使用
System.Func
创建一个责任链管道,管道中的每个函数都包含对下一个函数的引用

在构建管道时,我无法通过引用传递内部函数,因为重新分配管道函数会引发StackOverflowException,例如:

Func<string, Func<string, string>, string> handler1 = (s, next) => {
    s = s.ToUpper();
    return next.Invoke(s);
};

Func<string, string> pipeline = s => s;
pipeline = s => handler1.Invoke(s, pipeline);

pipeline.Invoke("hello"); // StackOverFlowException
Func handler1=(s,next)=>{
s=s.ToUpper();
返回next.Invoke(s);
};
Func管道=s=>s;
pipeline=s=>handler1.Invoke(s,pipeline);
pipeline.Invoke(“hello”);//StackOverFlowException
我可以通过结束来解决这个问题:

Func<string, Func<string, string>, string> handler1 = (s, next) => {
    s = s.ToUpper();
    return next.Invoke(s);
};

Func<Func<string, string>, Func<string, string>> closure = 
    next => s => handler1.Invoke(s, next);

Func<string, string> pipeline = s => s;
pipeline = closure.Invoke(pipeline);

pipeline.Invoke("hello");
Func handler1=(s,next)=>{
s=s.ToUpper();
返回next.Invoke(s);
};
Func闭包=
next=>s=>handler1.Invoke(s,next);
Func管道=s=>s;
管道=closure.Invoke(管道);
调用(“hello”);
但是,我想知道是否有更有效的方法来构建这一函数链,可能是使用表达式?

使用表达式,由于编译表达式的成本,过程的“构建”部分肯定效率较低,可能至少比连接
Func
s慢两个数量级

深入研究表达式-管道中的元素是表达式本身,而不是
Func
s,可以通过重写来创建运行时效率更高的实现。设置速度较慢,但基本上每次向您提供一个元素表达式时,如:

Expression<Func<string, Func<string, string>, string>> handler1 = (s, next) => 
    next.Invoke(s.ToUpper());
表达式句柄1=(s,next)=>
next.Invoke(s.ToUpper());
重写它,使
next
中应该包含的内容的主体直接内联到表达式树中出现的
next.Invoke(…)

但这确实限制了对表达式体元素的使用(实际上,您只需要让任何复杂处理程序的主体调用helper函数,而不是执行它们需要内联执行的任何工作)


我试图在某个地方找到一个这样的例子,但是我想不出一个好的例子。祝你好运

在我看来,责任链就像一个链表。通常,它会创建一个类来封装包含对链中下一个处理程序的引用的每个处理程序。但如果您想使用
Func
样式,我们可以使用过程样式执行类似的操作:

此处演示:

publicstaticvoidmain()
{
Func handler1=(s)=>{
s=s.ToUpper();
返回s;
};
Func handler2=(s)=>{
s=s.TrimStart();
返回s;
};
Func-chain=ChainBuilder(handler1,handler2);
Console.WriteLine(链(“你好”));
}
静态函数链生成器=(f1,f2)=>s=>{
s=f1(s);
如果(f2!=null){
s=f2(s);
}
返回s;
};
我正在做的是创建一个高阶函数来构建链。 另一个演示使用相同的想法链接3个处理程序:


但是,我建议为此创建一个类:。从封装和抽象的角度来看,它更好,并且易于扩展,以便为每个处理程序添加特定的配置。

那又如何?这样,您可以构建任意长度的链

void Main()
{
    Func<string, string> f1 = x => x.Replace("*", string.Empty);
    Func<string, string> f2 = x => x.Replace("--", string.Empty);
    Func<string, string> f3 = x => x.ToUpper();

    //Func<string, string> pipeline = x => f3(f2(f1(x)));
    Func<string, string> pipeline = Pipeline(f1, f2, f3);

    pipeline.Invoke("te-*-st").Dump(); // prints "TEST"
}

Func<T, T> Pipeline<T>(params Func<T, T>[] functions)
{
    Func<T, T> resultFn = x => x;

    for (int i = 0; i < functions.Length; i++)
    {
        Func<T, T> f = functions[i];
        Func<T, T> fPrev = resultFn;
        resultFn = x => f(fPrev(x));
    }

    return resultFn;
}
void Main()
{
Func f1=x=>x.Replace(“*”,string.Empty);
Func f2=x=>x.Replace(“--”,string.Empty);
Func f3=x=>x.ToUpper();
//Func管道=x=>f3(f2(f1(x));
Func管道=管道(f1、f2、f3);
pipeline.Invoke(“te-*-st”).Dump();//打印“TEST”
}
Func管道(参数Func[]函数)
{
Func resultFn=x=>x;
for(int i=0;if(fPrev(x));
}
返回结果n;
}

只是想知道,如果您知道问题是管道的重新分配(这是正确的),为什么不创建一个新变量呢?我的额外管道支持任意数量的函数。声明一个新变量没有帮助,因为从根本上说,您仍然必须通过引用来传递以前的函数。忽略上面的内容,新变量是解决方案-我会责怪睡眠不足:)很高兴提供帮助。你介意把你的答案和你的解决方案一起贴出来吗?我更喜欢一个而不是三个已发布的答案这实际上不是一个责任链管道,但您的回答告诉我一个简单的解决方案,即定义一个局部变量来引用前面的函数。我早就应该想到这一点。我发现一篇讨论类似内容的帖子很不错,所以我可能会对此进行深入探讨。严格来说,您的
Func
版本没有实现责任链,因为每个处理程序都不负责决定是否应执行下一个处理程序。我同意关于显式处理程序类型的封装,我只想提供这两个选项,因为我仍然能够提供基本上回退到Func@Ben福斯特:似乎没有简单的解决方案可以使用
Func
实现责任链。因为在这个模式中,链中的每个处理程序都需要是有状态的(存储一个引用),但是
Func
是无状态的。处理程序在确定是否应执行下一个处理程序的高阶函数中引用,这是函数编程风格:。被接受的答案也是相同的,只是写的方式不同。@Ben Foster:如果你需要不同的方式来决定是否应该执行下一个处理程序,你可以写不同的链生成器,它们有不同的逻辑来决定如何执行下一个处理程序。在我看来,我们应该编写一个像上面这样的类,这是处理这个问题最自然的方法
void Main()
{
    Func<string, string> f1 = x => x.Replace("*", string.Empty);
    Func<string, string> f2 = x => x.Replace("--", string.Empty);
    Func<string, string> f3 = x => x.ToUpper();

    //Func<string, string> pipeline = x => f3(f2(f1(x)));
    Func<string, string> pipeline = Pipeline(f1, f2, f3);

    pipeline.Invoke("te-*-st").Dump(); // prints "TEST"
}

Func<T, T> Pipeline<T>(params Func<T, T>[] functions)
{
    Func<T, T> resultFn = x => x;

    for (int i = 0; i < functions.Length; i++)
    {
        Func<T, T> f = functions[i];
        Func<T, T> fPrev = resultFn;
        resultFn = x => f(fPrev(x));
    }

    return resultFn;
}