如何确定我是否处于 VB.NET 中 For Each 语句的最后循环中?
13 回答
通常,您可以在其For Each
上执行的集合实现IEnumerator
接口。这个接口只有两个方法,MoveNext
和Reset
一个属性,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 错误 正确
使用 For 循环而不是 ForEach 可能会更容易。但是,类似地,您可以在 ForEach 循环中保留一个计数器,看看它是否等于yourCollection.Count - 1
,那么您就处于最后一次迭代中。
使用 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
诀窍是获取当前值的副本,然后要求枚举器尝试移动到下一个值。如果失败,则您制作的副本确实是最后一个值,否则还有更多。
简短的回答:你不能
长答案: 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;
}
这是一个简单的事情,我不知道它是否在政治上正确,但它有效
for each item in listOfThings
if(item = listOfThings.last)then
'you found the last loop
end if
next
检查元素是否是容器的最后一个元素。
你为什么要这样做?
您可以在循环之后放置指令。(你在最后一个元素上执行)
使用 For 循环而不是 ForEach 会更容易,但是您可以执行类似的操作
If item.Equals(itemCollection(itemCollection.Count)) Then
...
End If
在您的 ForEach 循环内...假设对象已正确覆盖 Equals 方法。
这可能比仅使用 For 循环或在单独的变量中跟踪当前索引更耗费资源。
我不确定这是否是正确的语法,自从我使用 VB 以来已经很长时间了。
不能使用标准的“For Each”循环。“for Each”循环的目的是让您专注于数据而不是基础集合。
我不断回到这篇文章,所以我决定坐下来思考这个问题,我想出了这个:
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()
每次迭代调用是否会对性能产生很大影响,但如果这会让你在晚上保持清醒,你总是可以将它分配给循环之外的变量。
此代码示例可能会有所帮助
For Each item in Collection
If ReferenceEquals(Collection.Item(Collection.Count - 1), item) Then
'do something with your last item'
End If
Next
如果您绑定到 IEnumerable。你必须在 foreach 循环内吗?如果不是,您可以在 foreach 循环之前声明一个变量。在循环期间设置它。然后,在循环之后使用它(如果它不为空)
好吧,解决方法很少假设您正在使用 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
For Each xObj In xColl
If xObj = xColl(xColl.Count) Then ...
Next