27

如何确定我是否处于 VB.NET 中 For Each 语句的最后循环中?

4

13 回答 13

60

通常,您可以在其For Each上执行的集合实现IEnumerator接口。这个接口只有两个方法,MoveNextReset一个属性,Current

基本上,当您For Each在集合上使用 a 时,它会调用该MoveNext函数并读取返回的值。如果返回值为True,则表示集合中有有效元素,并且元素通过Current属性返回。如果集合中没有更多元素,则MoveNext函数返回False并退出迭代。

从上面的解释中,很明显For Each不跟踪集合中的当前位置,因此您的问题的答案是简短的No

但是,如果您仍然想知道您是否在集合中的最后一个元素上,您可以尝试以下代码。它检查(使用 LINQ)当前项是否是最后一项。

For Each item in Collection
    If item Is Collection.Last Then
        'do something with your last item'
    End If
Next

重要的是要知道调用Last()集合将枚举整个集合。因此不建议调用Last()以下类型的集合:

  • 流式集合
  • 计算昂贵的集合
  • 突变倾向高的集合

对于这样的集合,最好为集合获取一个枚举器(通过GetEnumerator()函数),这样您就可以自己跟踪项目。下面是一个通过扩展方法实现的示例实现,该方法产生项目的索引,以及当前项目是集合中的第一项还是最后一项。

<Extension()>
Public Iterator Function EnumerateEx(Of T)(collection As IEnumerable(Of T))
    As IEnumerable(Of (value As T, index As Integer, isFirst As Boolean, isLast As Boolean))

    Using e = collection.GetEnumerator()
        Dim index = -1
        Dim toYield As T

        If e.MoveNext() Then
            index += 1
            toYield = e.Current
        End If

        While e.MoveNext()
            Yield (toYield, index, index = 0, False)
            index += 1
            toYield = e.Current
        End While

        Yield (toYield, index, index = 0, True)
    End Using
End Function

这是一个示例用法:

Sub Main()
    Console.WriteLine("Index   Value   IsFirst   IsLast")
    Console.WriteLine("-----   -----   -------   ------")

    Dim fibonacci = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89}

    For Each i In fibonacci.EnumerateEx()
        Console.WriteLine(String.Join("   ", $"{i.index,5}",
                                             $"{i.value,5}",
                                             $"{i.isFirst,-7}",
                                             $"{i.isLast,-6}"))
    Next

    Console.ReadLine()
End Sub

输出

索引值 IsFirst IsLast
----- ----- -------- ------
    0 0 真 假
    1 1 错误 错误
    2 1 错误 错误
    3 2 错误 错误
    4 3 错误 错误
    5 5 错误 错误
    6 8 错误 错误
    7 13 错误 错误
    8 21 错误 错误
    9 34 错误 错误
   10 55 错误 错误
   11 89 错误 正确
于 2010-10-28T10:12:52.170 回答
14

使用 For 循环而不是 ForEach 可能会更容易。但是,类似地,您可以在 ForEach 循环中保留一个计数器,看看它是否等于yourCollection.Count - 1,那么您就处于最后一次迭代中。

于 2008-12-17T23:34:51.157 回答
7

使用 foreach,您无法知道这一点,直到为时已晚(即您脱离了循环)。

请注意,我假设您使用的东西只有一个 IEnumerable 接口。如果您有一个列表、数组等,那么请按照此处使用 .Count 或类似的其他答案来找出有多少项目,因此您可以跟踪您在集合中的位置。

但是,仅使用 IEnumerable/IEnumerator,如果您使用 foreach,则无法确定是否还有更多。

如果您需要知道这一点,请自己使用 IEnumerable,这就是 foreach 所做的。

以下解决方案适用于 C#,但应该很容易转换为 VB.NET:

List<Int32> nums = new List<Int32>();
nums.Add(1);
nums.Add(2);
nums.Add(3);

IEnumerator<Int32> enumerator = nums.GetEnumerator();
if (enumerator.MoveNext())
{
    // at least one value available
    while (true)
    {
        // make a copy of it
        Int32 current = enumerator.Current;

        // determine if it was the last value
        // if not, enumerator.Current is now the next value
        if (enumerator.MoveNext())
        {
            Console.Out.WriteLine("not last: " + current);
        }
        else
        {
            Console.Out.WriteLine("last: " + current);
            break;
        }
    }
}
enumerator.Dispose();

这将打印:

not last: 1
not last: 2
last: 3

诀窍是获取当前值的副本,然后要求枚举器尝试移动到下一个值。如果失败,则您制作的副本确实是最后一个值,否则还有更多。

于 2008-12-17T23:41:04.967 回答
2

简短的回答:你不能

长答案: For Each 语句的语义中没有任何内容可以让您确定您是在运行第一次、最后一次还是任何特定的迭代。

For Each 内置于 IEnumerable 和 IEnumerable<> 接口中,其行为取决于您调用的实现。对于您正在迭代的集合以每次都以不同的顺序返回元素,这是有效的,尽管令人困惑。幸运的是, List<> 没有这样做。

如果您确实需要知道特定迭代是最后一个,您可以(提前)识别最后一个元素,然后在遇到该元素时采取不同的操作。

更容易检测第一次迭代(例如,通过布尔值),然后做一些不同的事情。

一个例子(C#,但 VB 类似):

StringBuilder result = new StringBuilder();
bool firstTime = true;
foreach(string s in names)
{
    if (!firstTime)
    {
        result.Append(", ");
    }

    result.Append(s);
    firstTime = false;
}
于 2008-12-17T23:41:21.330 回答
2

这是一个简单的事情,我不知道它是否在政治上正确,但它有效

for each item in listOfThings
   if(item = listOfThings.last)then 
       'you found the last loop
   end if
next 
于 2018-01-10T15:22:51.100 回答
1

检查元素是否是容器的最后一个元素。

你为什么要这样做?
您可以在循环之后放置指令。(你在最后一个元素上执行)

于 2008-12-17T23:29:17.563 回答
1

使用 For 循环而不是 ForEach 会更容易,但是您可以执行类似的操作

If item.Equals(itemCollection(itemCollection.Count)) Then
    ...
End If

在您的 ForEach 循环内...假设对象已正确覆盖 Equals 方法。

这可能比仅使用 For 循环或在单独的变量中跟踪当前索引更耗费资源。

我不确定这是否是正确的语法,自从我使用 VB 以来已经很长时间了。

于 2008-12-17T23:41:49.257 回答
1

不能使用标准的“For Each”循环。“for Each”循环的目的是让您专注于数据而不是基础集合。

于 2008-12-17T23:42:03.467 回答
1

我不断回到这篇文章,所以我决定坐下来思考这个问题,我想出了这个:

    For Each obj In myCollection
        Console.WriteLine("current object: {0}", obj.ToString)

        If Object.ReferenceEquals(obj, myCollection.Last()) Then
            Console.WriteLine("current object is the last object")
        End If
    Next

如果您愿意,您甚至可以将If...Then...End If语句缩减为一行。我不确定.Last()每次迭代调用是否会对性能产生很大影响,但如果这会让你在晚上保持清醒,你总是可以将它分配给循环之外的变量。

于 2013-02-27T21:25:57.703 回答
1

此代码示例可能会有所帮助

For Each item in Collection
  If ReferenceEquals(Collection.Item(Collection.Count - 1), item) Then
    'do something with your last item'
  End If
Next
于 2013-12-20T09:19:39.803 回答
0

如果您绑定到 IEnumerable。你必须在 foreach 循环内吗?如果不是,您可以在 foreach 循环之前声明一个变量。在循环期间设置它。然后,在循环之后使用它(如果它不为空)

于 2008-12-17T23:36:34.257 回答
0

好吧,解决方法很少假设您正在使用 CheckBoxList

那么这个代码示例可能会有所帮助:

Dim count As Integer = 0
Dim totalListCount As Integer = CheckBoxList.Items.Count()
For Each li In CheckBoxList.Items
         count += 1
           // This is the Last Item
      If count = totalListCount THEN
          // DO Somthing.....
      END IF
NEXT     
于 2012-07-25T13:54:08.540 回答
0
For Each xObj In xColl
    If xObj = xColl(xColl.Count) Then ...
Next
于 2019-11-18T13:12:37.703 回答