List<T>.ForEach 带索引

Posted

技术标签:

【中文标题】List<T>.ForEach 带索引【英文标题】:List<T>.ForEach with index 【发布时间】:2012-10-24 17:14:08 【问题描述】:

我正在尝试找到以下代码的 LINQ 等效项:

NameValueCollection nvc = new NameValueCollection();

List<BusinessLogic.Donation> donations = new List<BusinessLogic.Donation>();
donations.Add(new BusinessLogic.Donation(0, "", "", "");
donations.Add(new BusinessLogic.Donation(0, "", "", "");
donations.Add(new BusinessLogic.Donation(0, "", "", "");

for(var i = 0; i < donations.Count(); i++)

    // NOTE: item_number_ + i - I need to be able to do this
    nvc.Add("item_number_" + i, donations[i].AccountName);

我希望我可以使用类似的东西:

NameValueCollection nvc = new NameValueCollection();

List<BusinessLogic.Donation> donations = new List<BusinessLogic.Donation>();
donations.Add(new BusinessLogic.Donation(0, "", "", "");
donations.Add(new BusinessLogic.Donation(0, "", "", "");
donations.Add(new BusinessLogic.Donation(0, "", "", "");

donations.ForEach(x => nvc.Add("item_name_" + ??, x.AccountName);

但我还没有找到一种方法来确定循环在哪个迭代上。任何帮助将不胜感激!

【问题讨论】:

捐款是否有 .IndexOf() 方法? 您可以使用int i = 0; donations.ForEach(x=&gt; nvc.Add("item_name_" + i++, ...,但不确定它有多安全。 【参考方案1】:

LINQ 没有ForEach 方法,这是有充分理由的。 LINQ 用于执行查询。它旨在从某些数据源获取信息。它不是旨在改变数据源。 LINQ 查询不应引起副作用,这正是您在这里所做的。

List确实有一个 ForEach 方法,这就是您正在使用的方法。因为它实际上不在 System.Linq 命名空间中,所以从技术上讲它不是 LINQ 的一部分。

您的问题中的 for 循环没有任何问题。尝试以您尝试的方式更改它是错误的(从良好实践的角度来看)。

Here 是一个更详细地讨论此事的链接。

现在,如果您想忽略该建议并使用ForEach 方法,编写一个提供操作索引的方法并不难:

public static void ForEach<T>(this IEnumerable<T> sequence, Action<int, T> action)

    // argument null checking omitted
    int i = 0;
    foreach (T item in sequence)
    
        action(i, item);
        i++;
    

【讨论】:

好吧,我显然使用了错误的术语。我的Donation 集合确实有一个.ForEach() 方法。 ForEach 未定义为 IEnumerable 的扩展方法,因此不是 LINQ 的一部分。 @JamesHill 是List 的方法。这不是System.Linq中的方法 @Servy,我认为很明显我需要离开我的电脑一段时间。我认为咖啡是正常的。谢谢你让我直截了当。 +1 以获得该文章的链接。我想知道为什么没有 ForEach 的 LINQ 实现,但这很有意义。【参考方案2】:

如果你真的想使用 List.ForEach,很简单:

//[...]
int i=0;
donations.ForEach(x => nvc.Add("item_name_" + i++, x.AccountName);

【讨论】:

【参考方案3】:

这有点令人费解并创建了一个中间集合,但是怎么样:

donations.Select((x, i) => new Name = "item_name_" + i, x.AccountName)
    .ToList()
    .ForEach(x=> nvc.Add(x.Name, x.AccountName));

这使用the overload of Enumerable.Select which incorporates the index。

我不得不说,这样做并没有什么真正的好处。您使用中间集合创建了更多开销,恕我直言,您失去了原始 for 循环的可读性。

如果您愿意使用foreach 循环而不是List.ForEach,也可以跳过中间集合。请参阅@wageoghe's answer(再次强烈推荐)。

【讨论】:

【参考方案4】:

这是一篇旧帖子,但在 Google 中排名很高,所以我认为更通用的方法会更合适。 (另外,我往往会忘记这是如何完成的,这意味着我每次都必须谷歌......)

假设一个整数列表:

var values = new List&lt;int&gt;() 2, 3, 4, 0x100, 74, 0xFFFF, 0x0F0F ;

要迭代列表并创建索引,请执行以下操作:

values.Select((x, i) => new

    item = x,
    index = i
)
.ToList()
.ForEach(obj =>

    int value = obj.item;
    int valueIndex = obj.index;
);

【讨论】:

这样做的缺点是你失去了对values 的惰性评估,因为ToList() 将在应用ForEach 之前强制枚举整个源。例如。查看两种方法的输出差异in this program。【参考方案5】:

您是否有任何理由不使用Dictionary&lt;string, string&gt;,因为您的姓名/键似乎是独一无二的?这会更快,您可以使用ToDictionary 标准查询运算符。

此外,如果您确实希望使用扩展方法(尽管 Servy 说这里的 for 循环是正确的解决方案),那么您可以编写自己的方法 - 请参阅 here。

【讨论】:

【参考方案6】:

捎带@lc 的答案。

foreach (var x in donations.Select((d, i) => new ItemName = "item_name_" + i, AccountName = d.AccountName))

  nvc.Add(x.ItemName, x.AccountName);

【讨论】:

既然存在元组,您可以改用(d, i) =&gt; ($"item_name_i", d.AccountName) 来缩短new 的语法。然后使用x.Item1x.Item2,或者命名元组字段而不是使用x【参考方案7】:

这也可以使用聚合来完成。检查以下示例:

var data = new[]

    "A",
    "B",
    "C",
    "D"
;

var count = data.Aggregate(0, (index, item) =>

    Console.WriteLine("[0] => '1'", index, item);

    return index + 1;
);

Console.WriteLine("Total items: 0", count);

Aggregate 在这里充当for 语句。唯一的缺点是每次迭代都需要将索引的值加一并返回。

【讨论】:

【参考方案8】:

我喜欢这样做:

NameValueCollection nvc = new NameValueCollection();

List<BusinessLogic.Donation> donations = new List<BusinessLogic.Donation>();
donations.Add(new BusinessLogic.Donation(0, "", "", ""));
donations.Add(new BusinessLogic.Donation(0, "", "", ""));
donations.Add(new BusinessLogic.Donation(0, "", "", ""));

Enumerable
    .Range(0, donations.Count())
    .ToList()
    .ForEach(i => nvc.Add("item_number_" + i, donations[i].AccountName));

【讨论】:

【参考方案9】:

一个糟糕的解决方案是简单地使用 select 并返回一个无用的值。

items.toList().Select((el, index) =>

    el.doStuff();
    return 0;
);

【讨论】:

我刚做了这个。不要告诉任何人,哈哈。 @JasonC 你的秘密对我来说是安全的 这段代码的问题几乎和单词一样多。它什么也不做,因为该语句没有被执行。并且当拼写被纠正时,ToList 是多余的。 Select 主体对 index 没有任何作用。执行时,它只会返回一个零列表并(可能)更改源对象,这不会被询问。它不会向NameValueCollection nvc 添加任何内容。糟糕,当然。 @GertArnold 用它的伪代码来传达这个概念。不是可粘贴的样品。如果你要抱怨拼写,你应该知道句子不以“And”开头。 ToList 不是多余的,因为它暗示 items 是需要转换的标准类型数组。它不需要对索引做任何事情,它只是表明它在你需要时可用。 foreach 首先不需要返回任何东西,所以我不知道你为什么抱怨这个。 伪代码在答案中并不是很有用。此外,已经有足够多的答案表明Select 的过载。并告诉如何在实际代码中使用它。【参考方案10】:

C# 7(大约 2017 年)添加了更简洁的元组语法,可以与 Select 的索引形式一起使用(无需手动计算索引),以提供简洁明了的语法。例如:

foreach ((var item, int n) in TheItems.Select((i,n)=>(i,n))) 
    // item is the current item, n is its index

或者,如果您愿意:

foreach (var element in TheItems.Select((item,n)=>(item,n))) 
    // element.item is the current item, element.n is its index

所以在 OP 的情况下,例如:

foreach ((var item, int n) in donations.Select((i,n)=>(i,n)))
    nvc.Add($"item_numbern", item.AccountName);

或者,如果您愿意:

foreach ((var k, var v) in donations.Select((i,n)=>($"item_number_n",i.AccountName)))
    nvc.Add(k, v);

或者,类似于this answer,但略显冗长,但请注意,这将通过调用ToList() 杀死惰性求值,这会在调用ForEach() 之前强制枚举整个源:

TheItems.Select((item,n)=>(item,n)).ToList().ForEach(element =>  
    // element.item is the current item, element.n is its index
);

【讨论】:

我觉得这个解决方案应该已经在这里了——嗯,基于这个Select 过载有几个答案。这里有什么新东西? @GertArnold foreach over Select,不维护索引计数器,代码更简洁。大多数“基于”选择的答案都会做一些额外的事情或扼杀懒惰的评估。 这里唯一重要的是Select 重载。你让它看起来你是第一个提到它的人。即便如此,真的没有必要添加无数种如何使用它。与此同时,人们可能想知道为什么没有人直接使用SelectToList 而没有Foreachforeach 直接从donations 创建nvc。那将是最不冗长的解决方案。不过,我不觉得有任何添加另一个答案的冲动。 @GertArnold 在一般意义上(尽管在 OP 的情况下不是问题),使用 ToList() 将在继续之前强制枚举整个源系列。因此,如果源是一些惰性生成的 IEnumerable,您将失去那里的惰性求值,这在某些情况下可能是一个问题。 @GertArnold Re:直接创建nvc,an.dreas.k's answer 非常接近,但实际上没有任何方法可以生成这样的NameValueCollectionToDictionary() 无济于事(但devdigital 的建议将使其成为可能),虽然您可以使用例如构建 NVC。 Aggregate 之类的,此时您不妨使用Add。此外,尚不清楚 OP 是否打算创建新的 NVC 或添加到现有的 NVC,但从问题来看,它看起来更像后者。【参考方案11】:

试试这个 -

donations.ForEach(x =>
         
             int index = donations.IndexOf(x);
             nvc.Add("item_name_" + index, x.AccountName);
         );

【讨论】:

最好用索引枚举一个中间集合(@lc 的答案),而不是在每次迭代时搜索列表 (.IndexOf) 如果一个项目多次出现在列表中会怎样? (请注意,在 OP 的代码中,所有项目都是相同的。)毕竟,这不仅仅是关于糟糕的性能。它甚至不工作 那么,有没有其他方法可以在不创建中间集合的情况下找到索引..? @Sevy - 哎呀..!!是的,现在对我来说很有意义。完全忘记唯一性部分。 Downvotes 现在光荣地接受了。 :) @Sevy - 是的,谢谢Servy 现在明白了。这就是为什么我说至少告诉我哪里错了。

以上是关于List<T>.ForEach 带索引的主要内容,如果未能解决你的问题,请参考以下文章

为啥 List<T>.ForEach 允许修改其列表?

List<T> 将 foreach 循环中的所有项目覆盖为最后一个值

泛型集合

c#并行foreach循环查找索引

C#中如何用for循环遍历List<类>?

在索引处的 List<T> 中添加值的 C# 方法