【问题标题】:List<T>.ForEach with indexList<T>.ForEach 带索引
【发布时间】: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++, ...,但不确定它有多安全。

标签: c# linq generics


【解决方案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_number{n}", 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:直接创建nvcan.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 现在明白了。这就是为什么我说至少告诉我哪里错了。
            猜你喜欢
            • 2015-11-19
            • 1970-01-01
            • 1970-01-01
            • 2011-06-29
            • 2016-08-27
            • 1970-01-01
            • 2010-10-19
            • 2010-10-06
            相关资源
            最近更新 更多