3

我对 C# 很陌生,并试图使用 lambda 表达式。

我有一个对象列表。我想从列表中选择项目并对所选项目执行 foreach 操作。我知道我可以在不使用 lambda 表达式的情况下做到这一点,但我想如果这可以使用 lambda 表达式。

所以我试图达到类似的结果

        List<UserProfile> users = new List<UserProfile>();
       ..load users with list of users
        List<UserProfile> selecteditem = users.Where(i => i.UserName=="").ToList();
        foreach(UserProfile item in selecteditem)
        {
            item.UserName = "NA";
        }

有可能做

      users.Where(i => i.UserName=="").ToList().ForEach(i=>i.UserName="NA");

但不是这样的

     users.select(i => i.UserName=="").ForEach(i=>i.UserName="NA");

有人可以解释这种行为..

4

2 回答 2

13

让我们从这里开始:

我有一个对象列表。

重要的是要理解,虽然准确,但该语句让 ac# 程序员想要更多。什么样的对象?在 .Net 世界中,始终牢记您正在使用的特定类型的对象是值得的。在这种情况下,该类型是UserProfile. 这似乎是一个附带问题,但它会很快变得与特定问题更加相关。你想说的是:

我有一个 UserProfile 对象列表。

现在让我们看看你的两个表达式:

users.Where(i => i.UserName=="").ToList().ForEach(i=>i.UserName="NA");

users.Where(i => i.UserName=="").ForEach(i=>i.UserName="NA");

不同之处(除了只有第一个编译或工作之外)是您需要调用.ToList()Where()函数的结果转换为 List 类型。现在我们开始明白为什么在使用 .Net 代码时总是要考虑类型,因为现在你应该想知道,“那么我在使用什么类型呢?” 我很高兴你问。

.Where()函数产生一个IEnumerable<T>类型,实际上它本身并不是一个完整的类型。它是一个接口,描述了实现它的契约的类型将能够做的某些事情。IEnumerable 接口一开始可能会让人感到困惑,但要记住的重要一点是它定义了可以与foreach循环一起使用的东西。这是它的唯一目的。.Net 中可以与 foreach 循环一起使用的任何东西:数组、列表、集合——它们几乎都实现了 IEnumerable 接口。您还可以循环访问其他内容。以字符串为例。您现在拥有的许多需要 List 或 Array 作为参数的方法只需将参数类型更改为 IEnumerable 即可变得更加强大和灵活。

.Net 还可以轻松创建将与此接口一起使用的基于状态机的迭代器。这对于创建本身不包含任何项目但知道如何以特定方式循环不同集合中的项目的对象特别有用。例如,我可能只遍历大小为 20 的数组中的项目 3 到 12。或者可能按字母顺序遍历项目。这里重要的是我可以做到这一点而无需复制或复制原件。这使得它在内存方面非常高效,并且它的结构使得您可以轻松地将不同的迭代器组合在一起以获得非常强大的结果。

IEnumerable<T>类型特别重要,因为它是构成 linq 系统核心的两种类型之一(另一种是 IQueryable)。您可以使用的大多数.Where().Select().Any()linq 运算符都定义为IEnumerable的扩展。

但是现在我们遇到了一个例外:ForEach(). 此方法不是IEnumerable 的一部分。它被直接定义为List<T>类型的一部分。因此,我们再次看到,始终了解您使用的类型非常重要,包括构成完整语句的每个不同表达式的结果。

深入了解为什么此特定方法不直接属于 IEnumerable 也是很有指导意义的。我相信答案在于 linq 系统从函数式编程世界中汲取了很多灵感。在函数式编程中,您希望拥有只做一件事且没有副作用的操作(函数)。理想情况下,这些函数不会改变原始数据,而是返回新数据。该ForEach()方法隐含地是关于创建改变数据的不良副作用。这只是糟糕的功能风格。此外,ForEach() 会破坏方法链接,因为它不会返回新的 IEnumerable。

这里还有一课要学。让我们看一下您的原始代码段:

List<UserProfile> users = new List<UserProfile>();
// ..load users with list of users
List<UserProfile> selecteditem = users.Where(i => i.UserName=="").ToList();
foreach(UserProfile item in selecteditem)
{
    item.UserName = "NA";
}

我之前提到过一些可以帮助您显着改进此代码的内容。还记得关于如何让 IEnumerable 项在集合上循环而不复制它的那一点吗?想想如果你用这种方式编写代码会发生什么,而不是:

List<UserProfile> users = new List<UserProfile>();
// ..load users with list of users
var selecteditem = users.Where(i => i.UserName=="");
foreach(UserProfile item in selecteditem)
{
    item.UserName = "NA";
}

我所做的只是删除对 的调用.ToList(),但一切仍然有效。唯一改变的是我们避免了复制整个列表。那应该使这段代码更快。在某些情况下,它可以使代码更快。需要记住的一点:在使用 linq 运算符方法时,通常最好避免调用.ToArray().ToList()尽可能避免调用,而且可能比您想象的要多得多。

至于foreach() {...}vs .Foreach( ... ):前者仍然是非常合适的风格。

于 2013-07-18T20:53:47.633 回答
6

当然,这很简单。 List有一个ForEach方法。没有这样的方法或扩展方法IEnumerable

至于为什么一个人有方法而另一个人没有,这是一种观点。如果你对他感兴趣的话,Eric Lippert会在博客上讨论这个话题。

于 2013-07-18T20:45:05.787 回答