【问题标题】:How do I know if I'm iterating on the last item of the collection?我如何知道我是否正在迭代集合的最后一项?
【发布时间】:2010-12-26 20:03:01
【问题描述】:

我想在我正在迭代的Dictionary 的最后一个KeyValuePair 上做一些不同的事情。

For Each item In collection
    If ItsTheLastItem
        DoX()
    Else
        DoY()
    End If
Next 

这可能吗?

重新编辑:我没有使用字典的值,我实际上使用的是KeyValuePairs 的列表。我已经转换了它们,后来没有注意到,我傻了。

【问题讨论】:

标签: c# vb.net collections foreach iteration


【解决方案1】:

我会制作你自己的扩展方法。这里的实现保证你只遍历一次集合。

static class IEnumerableExtensions {
    public static void ForEachExceptTheLast<T>(
        this IEnumerable<T> source,
        Action<T> usualAction,
        Action<T> lastAction
    ) {
        var e = source.GetEnumerator();
        T penultimate;
        T last;
        if (e.MoveNext()) {
            last = e.Current;
            while(e.MoveNext()) {
                penultimate = last;
                last = e.Current;
                usualAction(penultimate);
            }
            lastAction(last);
        }
    }
}    

用法:

Enumerable.Range(1, 5)
          .ForEachExceptTheLast(
              x => Console.WriteLine("Not last: " + x),
              x => Console.WriteLine("Last: " + x)
);

输出:

Not last: 1
Not last: 2
Not last: 3
Not last: 4
Last: 5

【讨论】:

    【解决方案2】:

    除非您想自己实现foreach,否则请使用计数器(C# 代码):

    int count = collection.Count;
    int counter = 0;
    
    foreach(var item in collection)
    {
      counter++;
      if(counter == count)
        DoX();
      else
        DoY();
    }
    

    请注意,这仅适用于非流式IEnumerable&lt;T&gt;,此外,根据实现,Count 会导致集合被遍历两次。

    【讨论】:

    • 这就是我要做的,我想。我希望每个集合都有一个索引 - 它内部不是一个数组吗?
    • @Camilo - 索引器由继承自 ICollection 的 IList 接口提供 - 一些集合实现了后者或 IEnumerable,因此没有索引器
    • 不,你不能做出这样的假设。例如,您可以迭代 LinkedList,但它没有数组作为内部数据结构。关于您对 Jani 问题的评论 - 您如何获得您的收藏?你说它是流式的,那么这种方法也不会很好地工作,这取决于Count 在流式集合上的实现。
    • 是的,这就是 for 循环的用途。为什么在 foreach 中使用计数器?
    • 因为如果你手头有一个索引器,你只能使用for 循环。如果一个集合只实现了IEnumerable,你就没有索引器。
    【解决方案3】:

    为什么不使用:

    List<string> list = new List<string>();
    // add items
    
    foreach (string text in list)
    {
        if (text.Equals(list[list.Count - 1]))
        {
            // last item
        }
        else
        {
            // not last item
        }
    }
    

    【讨论】:

    • 这很好用,为什么不这么简单,如果是,为什么没有 .IsLast 方法为列表项返回布尔值
    【解决方案4】:

    将集合转换为列表(如 LINQ 的 ToList())并使用 for(int i = 0)... 循环进行迭代。

    【讨论】:

    • 是集合,不是列表,所以无法索引。
    • 我在代码流程中没有注意到之前这样做了。但是,现在我甚至可以使用For Each 来做到这一点,恕我直言,这在语义上更甜。换句话说,接受。
    【解决方案5】:

    IEnumerable&lt;T&gt; 无法自然而然地做到这一点,因为在尝试进入下一个之前,您不会知道是否还有其他元素。

    但是,在我的 MiscUtil library 中,我有一个名为 SmartEnumerable 的东西,它会提前读取一项,以提供以下属性:

    • IsFirst
    • IsLast
    • Index
    • Value

    有关使用说明,请参阅this page。例如:

    For Each item In collection.ToSmartEnumerable()
        If item.IsFirst
            DoX()
        Else
            DoY()
        End If
    Next 
    

    您需要使用item.Value 来获取当前项目的值。

    关于迭代字典的一点是 - 它们实际上没有任何顺序。因此,虽然您可以迭代并且 会有第一个条目和最后一个条目,但您不应该假设它们将分别是添加的第一个条目和添加的最后一个条目。对于您的用例来说,这可能不是问题,但您应该意识到这一点。

    【讨论】:

    • Jon Skeet 可以制作一个通用实用程序库,其名称类似于 MiscUtil,让您感到自豪。
    【解决方案6】:

    我刚刚有了一个不使用计数器的想法。

    For Each item In collection.GetRange(0, collection.Count - 1)
        DoY(item)
    Next
    DoX(collection.Last)
    

    编辑:对不起,这是一个列表,我之前已经转换了它,智能感知给了我列表方法而不是字典方法。

    【讨论】:

    • 我使用您的想法从用户输入的 3 或 4 个八位字节构建 IP 字符串。详情见我的回答
    【解决方案7】:
            foreach (var item in collection.Reverse().Skip(1))
            {
    
            }
            // do here the stuff related to list.LastOrDefaut()
    

    另一种方法是保持迭代索引,并在点击collection.Length-2collection.Count()-2 时停止循环并对最后一个元素执行任何操作。

    但正如@BrokenGlass 所说,请注意它可能对您的应用程序产生的副作用。

    通常您必须特别小心地使用 Linq。 例如,每次迭代 linq to sql 集合时,都会再次执行查询,因此最好急切通过简单地调用.ToList() 并在内存集合中尽可能多地使用它来运行它。你想要的。

    【讨论】:

    • 即使它“看起来不错”,我也不会为了对一个元素做一些特殊的事情而对大型集合这样做——这种方法需要对整个集合进行缓冲而不是流式传输。
    • 看起来不错,但Reverse() 不是函数(即它不返回List),因此这是无效代码。
    • @Camilo 不一定是 List,只是一个变量名,为了更清楚,我将其更改为 collection。然后你可以测试它,如果你这样做了,你就可以撤销你的快速否决。
    • @Jani 我没有反对,我赞成和反对,因为它只是无法编译。我不能在Reverse() 上调用Skip(),因为Reverse() 不是一个返回我可以调用Skip() 的函数的函数,不幸的是。
    • 首先抱歉,然后这里是 Skip 和 Reverse 的签名,“public static IEnumerable Skip(this IEnumerable source, int count);” , "public static IEnumerable Reverse(这个 IEnumerable 源);"它们都接受 IEnumerable 并返回 IEnumerable。可能你的程序出了问题。
    【解决方案8】:

    我根据上面的@CamiloMartin 回答创建了一个从 4 个八位字节构建 IP 字符串的解决方案。

     String sIP;
     StringBuilder sb = new StringBuilder();
     List<String> lstOctets= new List<string>{ usrIP1.Text, usrIP2.Text, usrIP3.Text, usrIP4.Text };
     foreach (String Octet in lstOctets.GetRange(0,lstOctets.Count - 1))
     {
        sb.Append(string.Format("{0:d}.", Octet));
     }
     if (lstOctets.Count == 4)
        sb.Append(string.Format("{0:d}", lstOctets[lstOctets.Count - 1]));
     else
         sb.Append(string.Format("{0:d}.0", lstOctets[lstOctets.Count - 1]));
     sIP = sb.ToString();
    

    【讨论】:

    • 我很高兴我的 sn-p 能帮上忙 :) 有趣的是,自从一年半前我转向 C# 以来,现在甚至我自己的 VB 代码看起来都与我。
    【解决方案9】:

    使用字典,您有多种选择

    Dim dictionary as new Dictionary(of String, MyObject)
    For Each item in dictionary
        // item is KeyValue pair
    Next
    
    For Each item in dictionary.Keys
        // item is String
    Next
    
    For Each item in dictionary.Keys
        // item is MyObject
    Next
    

    我建议您遍历 ValueCollection 并使用计数器或在当前项和最后一项之间进行 Equals 比较(这可能会更慢,但您保存一个计数器变量;) 不要忘记添加一个空列表的检查。

    For Each item in dictionary.Values
    
        If dictionary.Count > 0 AndAlso _
            item.Equals(dictionary.Values(dictionary.Count - 1)) Then
            Console.WriteLine("Last Item")
        Else
            Console.WriteLine("Some other Item")
        End If
    
    Next
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-30
      • 2014-12-15
      • 1970-01-01
      • 2020-03-26
      • 1970-01-01
      • 2017-05-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多