VB.NET で For Each ステートメントの最終ループにいるかどうかを確認するにはどうすればよいですか?
13 に答える
通常、インターフェイスFor Each
を実装する上で実行できるコレクション。IEnumerator
このインターフェイスには、 と の 2 つのメソッドと 1 つのプロパティしかあり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 偽 真
おそらく、ForEach の代わりに For ループを使用する方が簡単でしょう。ただし、同様に、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;
}
要素がコンテナーの最後の要素であるかどうかを確認します。
なぜそれをしたいのですか?
ループの後に命令を配置するだけです。(最後の要素で実行すること)
ForEach の代わりに For ループを使用する方が簡単ですが、次のようなこともできます。
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
必要に応じて、ステートメントを 1 行に減らすこともできます。すべての反復で呼び出すこと.Last()
がパフォーマンスに大きな影響を与えるかどうかはわかりませんが、それが原因で夜目が覚めている場合は、いつでもループ外の変数に割り当てることができます。
IEnumerable に縛られている場合。foreach ループ内にいる必要がありますか? そうでない場合は、foreach ループの直前に変数を宣言できます。ループ中に設定してください。次に、ループの後に使用します(nullでない場合)
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