C#中的管道转发

继续我在C#中表达F#想法的调查 ,我想要一个管道前进算子。 对于包含在IEnumerable中的任何东西,我们已经拥有它,因为你可以.NextFunc()到你心中的内容。 但是,例如,如果最后有任何类似折叠的缩减,则无法将其结果输入函数。

这里有两个扩展方法,我想知道是否有其他人尝试过这个,如果这是一个好主意(编辑:现在使用Earwicker的可能包含):

public static void Pipe(this T val, Action action) where T : class { if (val!=null) action(val); } public static R Pipe(this T val, Func func) where T : class where R : class { return val!=null?func(val):null; } 

然后你可以写下这样的东西:

 Func readlines = (f) => File.ReadAllLines(f); Action writefile = (f, s) => File.WriteAllText(f, s); Action RemoveLinesContaining = (file, text) => { file.Pipe(readlines) .Filter(s => !s.Contains(text)) .Fold((val, sb) => sb.AppendLine(val), new StringBuilder()) .Pipe((o) => o.ToString()) .Pipe((s) => writefile(file, s)); }; 

(我知道,filter==在C#中的位置,并且折叠==聚合,但我想要自己动手,我本可以完成WriteAllLines,但这不是重点)

编辑:根据Earwicker的评论进行更正(如果我理解正确的话)。

我没有使用原始管道,但我尝试将所有引用都引入到Maybe monad中:

 public static class ReferenceExtensions { public static TOut IfNotNull(this TIn v, Func f) where TIn : class where TOut: class { if (v == null) return null; return f(v); } } 

然后假设您有一个对象模型,它允许您按名称查找RecordCompany,然后在该RecordCompany中查找Band,Band的成员,并且其中任何一个都可能返回null,因此这可能会抛出NullReferenceException:

 var pixiesDrummer = Music.GetCompany("4ad.com") .GetBand("Pixes") .GetMember("David"); 

我们可以解决这个问题

 var pixiesDrummer = Music.GetCompany("4ad.com") .IfNotNull(rc => rc.GetBand("Pixes")) .IfNotNull(band => band.GetMember("David")); 

嘿presto,如果这些转换中的任何一个返回null,pixiesDrummer将为null。

如果我们能够做出运算符重载的扩展方法,那不是很好吗?

 public static TOut operator| (TIn v, Func f) 

然后我可以将我的过渡lambdas像这样管道:

 var pixiesDrummer = Music.GetCompany("4ad.com") | rc => rc.GetBand("Pixes") | band => band.GetMember("David"); 

如果System.Void被定义为一个类型并且Action真的只是Func <...,Void>,那也不是很好吗?

更新: 我在博客上写了一下这背后的理论 。

更新2:原始问题的另一种答案,大致是“你如何在C#中表达F#管道转发运算符?”

管道输送是:

 let (|>) xf = fx 

换句话说,它允许您以相反的顺序编写函数及其第一个参数:参数后跟函数。 它只是一个语法助手,有助于提高可读性,允许您使用任何函数的中缀表示法。

这正是C#中的扩展方法。 没有它们,我们必须写:

 var n = Enumerable.Select(numbers, m => m * 2); 

有了它们,我们可以写:

 var n = numbers.Select(m => m * 2); 

(忽略这样一个事实,即他们也让我们省略了类名 – 这是一个奖励,但也可以用于非扩展方法,就像在Java中一样)。

所以C#已经以不同的方式解决了同样的问题。

所以对于管道我不认为有期望检查null并且不调用管道function。 在许多情况下,函数参数可以很容易地取空并让它由函数处理。

这是我的实施。 我有PipePipeR 。 预先警告, PipeR不是正确的管道,而是针对目标处于相反位置进行curl的情况,因为备用过载允许有限的伪造参数。

关于假currying的好处是你可以在提供参数后输入方法名称,从而产生比使用lambda更少的嵌套。

 new [] { "Joe", "Jane", "Janet" }.Pipe(", ", String.Join) 

String.Join在最后一个位置有IEnumerable,所以这个工作。

 "One car red car blue Car".PipeR(@"(\w+)\s+(car)",RegexOptions.IgnoreCase, Regex.IsMatch) 

Regex.IsMatch的目标位于第一个位置,因此PipeR可以工作。

这是我的示例实现:

 public static TR Pipe(this T target, Func func) { return func(target); } public static TR Pipe(this T target, T1 arg1, Func func) { return func(arg1, target); } public static TR Pipe(this T target, T1 arg1, T2 arg2, Func func) { return func(arg1, arg2, target); } public static TR PipeR(this T target, T1 arg1, Func func) { return func(target, arg1); } public static TR PipeR(this T target, T1 arg1, T2 arg2, Func func) { return func(target, arg1, arg2); } 

虽然它不是一回事,但您可能对我的Push LINQ框架感兴趣。 基本上, IEnumerable要求感兴趣的一方从源中提取数据,Push LINQ允许您通过源推送数据,并且感兴趣的各方可以订阅与“另一个元素刚刚过去”相对应的事件,并且“数据已经完成”。

Marc Gravell和我已经实现了大多数标准的LINQ查询运算符,这意味着你可以编写针对数据源的查询表达式,并做一些有趣的事情,如流分组,多聚合等。

你的Pipe方法看起来很像Thrush Combinator。 我的实现非常简单。

 public static T Into(this T obj, Func f) { return f(obj); }