C#中的闭包和Lambda

11

我理解了闭包和lambda表达式的基本原则,但我正在尝试理解它们背后发生的事情,以及在我的代码中何时使用它们是/不是实际的。考虑以下示例,它接受一个名称集合并返回任何以字母C开头的名称...

    static void Main(string[] args)
    {
        List<string> names = new List<string>();
        names.AddRange(new string[]
        {
            "Alan", "Bob", "Chris", "Dave", "Edgar", "Frank"
        });


        names.FindAll(x => x.StartsWith("C")).ForEach(
            i => Console.WriteLine(i));

    }

首先,我是否有更直接的方式来编写这个表达式?其次,“FindAll”不会为保存匹配项的新集合分配内存吗?我确实看到语法更优雅了,但我想确保在处理更大的集合时不会遇到性能问题。编译器是否在幕后执行一些优化操作使我的担忧无效?

5个回答

15
是的,FindAll会创建一个新的列表。你需要使用"Where",它会返回一个IEnumerable对象,该对象知道如何循环遍历您现有的列表:
foreach (string name in names.Where(n => n.StartsWith("C") ) ) 
{
    Console.WriteLine(name);
}

但是该代码中没有闭包,因为没有局部变量需要捕获。


好的,也许我还不太了解闭包的基础。尽管如此,我在这里得到的所有答案都很棒,让我在学习道路上又前进了一步...谢谢大家。 - lJohnson
1
这向我解释了闭包和lambda之间的区别:“在那段代码中没有闭包,因为没有本地变量需要捕获。” - TLDR

12

其他回答建议使用 "Where" 是正确的。另一个要点是:您还可以使用查询推导语法使 "Where" 看起来更好:

   var query = from name in names where name.StartsWith("C") select name;
   foreach(var result in query) Console.WriteLine(result);

请注意,从风格上讲,我建议表达式没有副作用,语句总是有副作用。因此,我个人会使用foreach 语句而不是ForEach 子表达式来执行输出副作用。许多人不同意这一点,但我认为它使代码更清晰。


2

您说得对,使用List<T>.FindAll方法将创建并返回一个新的List<T>

如果您能使用LINQ,则有许多方法可在可能的情况下一次流式传输其结果一项,而不是返回完全填充的集合:

foreach (var i in names.Where(x => x.StartsWith("C")))
{
    Console.WriteLine(i);
}

IEnumerable<T> 上没有内置的 ForEach 方法,但如果您真的需要该功能,则编写自己的扩展非常简单:

names.Where(x => x.StartsWith("C")).ForEach(Console.WriteLine);

// ...

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

1
这正是我要做的事情。我还会提到列表创建可以简化为:List<string> names = new List<string>{"Alan", "Bob", "Chris", "Dave", "Edgar", "Frank"}; - Ryan Versaw

2

你应该使用Where而不是FindAllWhere会为您的条件迭代集合并允许您执行操作,而不是创建一个满足条件的新集合,然后迭代该集合并执行操作。


0

什么使一个表达式特别成为闭包是词法作用域,对吧?

string prefix = "C";  
// 前缀的值包含在作用域内  
names.FindAll(x => x.StartsWith(prefix)).ForEach(...);    

甚至可以这样:

Func filter = null;
{ string prefix = "C"; // 前缀的值包含在作用域内 filter = x => x.StartsWith (prefix); }
// 查找所有以“C”开头的名称 names.FindAll (filter).ForEach (...);

或者我有什么遗漏或做出了不必要的假设吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接