c# - 这个 C# 扩展方法是否不纯,如果是,代码是否错误?

标签 c# functional-programming extension-methods

我正在学习一些关于函数编程的知识,我想知道:

1) 如果我的 ForEach扩展方法是纯的?我这样调用它似乎违反了“不要弄乱传入的对象”,对吧?

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
  foreach ( var item in source )
     action(item);
}


static void Main(string[] args)
{
    List<Cat> cats = new List<Cat>()
    {
        new Cat{ Purring=true,Name="Marcus",Age=10},
        new Cat{ Purring=false, Name="Fuzzbucket",Age=25 },
        new Cat{ Purring=false, Name="Beanhead",Age=9 },
        new Cat{Purring=true,Name="Doofus",Age=3}
    };


    cats.Where(x=>x.Purring==true).ForEach(x =>
    {
        Console.WriteLine("{0} is a purring cat... purr!", x.Name);
    });

    // *************************************************
    //  Does this code make the extension method impure?
    // *************************************************
    cats.Where(x => x.Purring == false).ForEach(x =>
    {
        x.Purring = true; // purr,baby
    });

    // all the cats now purr
    cats.Where(x=>x.Purring==true).ForEach(x =>
    {
        Console.WriteLine("{0} is a purring cat... purr!", x.Name);
    });
}

public class Cat {
        public bool Purring;
        public string Name;
        public int Age;
}

2) 如果是不纯的,是不好的代码吗?我个人认为它使代码看起来比旧的 foreach ( var item in items) { blah; } 更干净。 , 但我担心因为它可能不纯,所以会弄得一团糟。

3) 如果返回 IEnumerable<T> 是否是错误代码而不是 void ?我会说只要它是不纯的,是的,它会是非常糟糕的代码,因为它会鼓励链接一些会修改链的东西。例如,这是错误的代码吗?

// possibly bad extension
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    foreach ( var item in source )
        action(item);

    return source;

}

最佳答案

不纯并不一定意味着糟糕的代码。许多人发现使用副作用来解决问题既简单又有用。关键是首先要知道如何以纯粹的方式做到这一点,这样你就会知道什么时候不纯是合适的:)。

.NET 在类型系统中没有纯粹的概念,因此接受任意委托(delegate)的“纯粹”方法总是不纯粹的,这取决于它的调用方式。例如,“Where”,又名“filter”,通常被认为是一个纯函数,因为它不修改其参数或修改全局状态。

但是,没有什么可以阻止您将此类代码放入 Where 的参数中。例如:

things.Where(x => { Console.WriteLine("um?"); 
                    return true; })
      .Count();

所以这绝对是对 Where 的不纯用法。 Enumerables 可以在迭代时做任何他们想做的事。

您的代码不好吗? 不。使用 foreach 循环同样“不纯”——您仍在修改源对象。我一直都这样写代码。将一些选择、过滤器等链接在一起,然后对其执行 ForEach 以调用一些工作。你是对的,它更干净,更容易。

示例:ObservableCollection。由于某种原因,它没有 AddRange 方法。那么,如果我想向其中添加一堆东西,我该怎么办?

foreach(var x in things.Where(y => y.Foo > 0)) { collection.Add(x)); } 

things.Where(x => x.Foo > 0).ForEach(collection.Add);

我更喜欢第二个。至少,我不认为它比第一种方式更糟糕。

什么时候是坏代码?当它在意想不到的地方产生副作用代码时。这是我使用 Where 的第一个示例的情况。即便如此,有时范围非常有限且用法很明确。

链接 ForEach

我已经编写了执行类似操作的代码。为了避免混淆,我会给它起另一个名字。主要的困惑是“这是立即评估还是惰性评估?”。 ForEach 意味着它将立即执行一个循环。但是返回 IEnumerable 的东西意味着将根据需要处理这些项目。所以我建议给它起另一个名字(“Process”、“ModifySeq”、“OnEach”……类似的名字),并让它变得懒惰:

public static IEnumerable<T> OnEach(this IEnumerable<T> src, Action<T> f) {
  foreach(var x in src) {
    f(x);
    yield return x;
  }
}

关于c# - 这个 C# 扩展方法是否不纯,如果是,代码是否错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/693628/

相关文章:

c# - 未应用 Wpf 按钮样式

haskell - (+1) 在这里计算了多少次?

scala - 将类型模式匹配转换为类型类

c# - 常规方法的 Action/Func 扩展方法

c# - 有趣的 "params of ref"功能,有什么解决方法吗?

c# - 如何更新 Windows 窗体中的 StatusStrip

c# - 使用 SlimDX 绘制 2D

c# - 在 Entity Framework 中使用 DbContext 运行异步调用

unit-testing - Elixir /ExUnit : how to test functions with system calls most elegantly?

c# - C# 扩展方法和 F# 管道转发运算符之间有什么关系?