Ich habe eine foreach
-Schleife und muss einige Logik ausführen, wenn das letzte Element aus der List
ausgewählt wird, z.
foreach (Item result in Model.Results)
{
//if current result is the last item in Model.Results
//then do something in the code
}
Kann ich wissen, welche Schleife zuletzt verwendet wird, ohne for-Schleife und Zähler zu verwenden?
Wenn Sie nur etwas mit dem letzten Element tun müssen (im Gegensatz zu etwas anders mit dem letzten Element, dann hilft die Verwendung von LINQ hier:
Item last = Model.Results.Last();
// do something with last
Wenn Sie mit dem letzten Element etwas anderes machen müssen, benötigen Sie Folgendes:
Item last = Model.Results.Last();
foreach (Item result in Model.Results)
{
// do something with each item
if (result.Equals(last))
{
// do something different with the last item
}
else
{
// do something different with every item but the last
}
}
Sie müssen jedoch wahrscheinlich einen benutzerdefinierten Vergleicher schreiben, um sicherzustellen, dass das Element mit dem von Last()
zurückgegebenen Element übereinstimmt.
Dieser Ansatz sollte mit Vorsicht angewendet werden, da Last
möglicherweise die Sammlung durchlaufen muss. Dies ist zwar für kleine Sammlungen kein Problem, aber wenn sie groß werden, kann dies Auswirkungen auf die Leistung haben. Es schlägt auch fehl, wenn die Liste doppelte Elemente enthält. In diesen Fällen kann so etwas geeigneter sein:
int totalCount = result.Count();
for (int count = 0; count < totalCount; count++)
{
Item result = Model.Results[count];
count++;
// do something with each item
if (count == totalCount)
{
// do something different with the last item
}
else
{
// do something different with every item but the last
}
}
Wie wäre es mit einer guten altmodischen for-Schleife?
for (int i = 0; i < Model.Results.Count; i++) {
if (i == Model.Results.Count - 1) {
// this is the last item
}
}
Oder mit Linq und dem foreach:
foreach (Item result in Model.Results)
{
if (Model.Results.IndexOf(result) == Model.Results.Count - 1) {
// this is the last item
}
}
Wie Chris zeigt, wird Linq arbeiten. Verwenden Sie Last (), um einen Verweis auf den letzten Eintrag in der Aufzählung zu erhalten. Wenn Sie nicht mit diesem Verweis arbeiten, geben Sie Ihren normalen Code ein. Wenn Sie jedoch mit diesem Verweis arbeiten, müssen Sie etwas extra tun. Der Nachteil ist, dass es immer O (N) -Komplexität ist.
Sie können stattdessen count () (das ist O(1)) verwenden, wenn das IEnumerable auch eine ICollection ist (dies gilt für die meisten der üblichen integrierten IEnumerables) und das Foreach mit einem Zähler kombinieren:
var i=0;
var count = Model.Results.Count();
foreach (Item result in Model.Results)
{
if(++i==count) //this is the last item
}
Wenn Sie Last()
für bestimmte Typen verwenden, wird die gesamte Sammlung durchlaufen.
Das heißt, wenn Sie eine foreach
machen und Last()
aufrufen, haben Sie zweimal geschlungen!, die Sie sicher in großen Sammlungen vermeiden möchten.
Dann ist die Lösung eine do while
-Schleife:
using (var enumerator = collection.GetEnumerator())
{
var last = !enumerator.MoveNext();
T current;
while(!last)
{
current = enumerator.Current;
//process item
last = !enumerator.MoveNext();
//process item extension according to flag; flag means item
}
}
Wenn der Auflistungstyp nicht vom Typ IList<T>
ist, durchläuft die Funktion Last()
alle Auflistungselemente.
foreach (var item in objList)
{
if(objList.LastOrDefault().Equals(item))
{
}
}
Wie Shimmy hervorgehoben hat, kann die Verwendung von Last () ein Leistungsproblem sein, beispielsweise wenn Ihre Sammlung das Live-Ergebnis eines LINQ-Ausdrucks ist. Um mehrere Iterationen zu verhindern, können Sie eine Erweiterungsmethode "ForEach" wie folgt verwenden:
var elements = new[] { "A", "B", "C" };
elements.ForEach((element, info) => {
if (!info.IsLast) {
Console.WriteLine(element);
} else {
Console.WriteLine("Last one: " + element);
}
});
Die Erweiterungsmethode sieht folgendermaßen aus (als zusätzlichen Bonus wird Ihnen auch der Index und wenn Sie das erste Element betrachten) mitgeteilt:
public static class EnumerableExtensions {
public delegate void ElementAction<in T>(T element, ElementInfo info);
public static void ForEach<T>(this IEnumerable<T> elements, ElementAction<T> action) {
using (IEnumerator<T> enumerator = elements.GetEnumerator())
{
bool isFirst = true;
bool hasNext = enumerator.MoveNext();
int index = 0;
while (hasNext)
{
T current = enumerator.Current;
hasNext = enumerator.MoveNext();
action(current, new ElementInfo(index, isFirst, !hasNext));
isFirst = false;
index++;
}
}
}
public struct ElementInfo {
public ElementInfo(int index, bool isFirst, bool isLast)
: this() {
Index = index;
IsFirst = isFirst;
IsLast = isLast;
}
public int Index { get; private set; }
public bool IsFirst { get; private set; }
public bool IsLast { get; private set; }
}
}
Die Iterator-Implementierung bietet das nicht. Ihre Sammlung kann eine IList
sein, auf die über einen Index in O (1) zugegriffen werden kann. In diesem Fall können Sie eine normale for
-Schleife verwenden:
for(int i = 0; i < Model.Results.Count; i++)
{
if(i == Model.Results.Count - 1) doMagic();
}
Wenn Sie die Anzahl kennen, aber nicht über Indizes zugreifen können (Ergebnis ist also eine ICollection
), können Sie sich selbst zählen, indem Sie eine i
im Körper der foreach
inkrementieren und mit der Länge vergleichen.
Das alles ist nicht perfekt elegant. Chris 'Lösung könnte die schönste sein, die ich bisher gesehen habe.
Was ist mit etwas einfacherem Ansatz?.
Item last = null;
foreach (Item result in Model.Results)
{
// do something with each item
last = result;
}
//Here Item 'last' contains the last object that came in the last of foreach loop.
DoSomethingOnLastElement(last);
Der beste Ansatz wäre wahrscheinlich, diesen Schritt nach der Schleife auszuführen: z.
foreach(Item result in Model.Results)
{
//loop logic
}
//Post execution logic
Oder wenn Sie etwas zum letzten Ergebnis tun müssen
foreach(Item result in Model.Results)
{
//loop logic
}
Item lastItem = Model.Results[Model.Results.Count - 1];
//Execute logic on lastItem here
Die akzeptierte Antwort funktioniert nicht für Duplikate in der Sammlung. Wenn Sie auf foreach
eingestellt sind, können Sie einfach Ihre eigenen Indexvariablen hinzufügen.
int last = Model.Results.Count - 1;
int index = 0;
foreach (Item result in Model.Results)
{
//Do Things
if (index == last)
//Do Things with the last result
index++;
}
Verbesserung der Daniel Wolf-Antwort Sie könnten noch eine weitere IEnumerable
stapeln, um mehrere Iterationen und Lambdas zu vermeiden.
var elements = new[] { "A", "B", "C" };
foreach (var e in elements.Detailed())
{
if (!e.IsLast) {
Console.WriteLine(e.Value);
} else {
Console.WriteLine("Last one: " + e.Value);
}
}
Die Implementierung der Erweiterungsmethode:
public static class EnumerableExtensions {
public static IEnumerable<IterationElement<T>> Detailed<T>(this IEnumerable<T> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
using (var enumerator = source.GetEnumerator())
{
bool isFirst = true;
bool hasNext = enumerator.MoveNext();
int index = 0;
while (hasNext)
{
T current = enumerator.Current;
hasNext = enumerator.MoveNext();
yield return new IterationElement<T>(index, current, isFirst, !hasNext);
isFirst = false;
index++;
}
}
}
public struct IterationElement<T>
{
public int Index { get; }
public bool IsFirst { get; }
public bool IsLast { get; }
public T Value { get; }
public IterationElement(int index, T value, bool isFirst, bool isLast)
{
Index = index;
IsFirst = isFirst;
IsLast = isLast;
Value = value;
}
}
}
".Last ()" hat für mich nicht funktioniert, also musste ich so etwas tun:
Dictionary<string, string> iterativeDictionary = someOtherDictionary;
var index = 0;
iterativeDictionary.ForEach(kvp =>
index++ == iterativeDictionary.Count ?
/*it's the last item */ :
/*it's not the last item */
);
Durch geringfügige Anpassungen des exzellenten Codes von Jon Skeet können Sie ihn sogar intelligenter gestalten, indem Sie den Zugriff auf den vorherigen und den nächsten Eintrag ermöglichen. Dies bedeutet natürlich, dass Sie 1 Element in der Implementierung vorlesen müssen. Aus Leistungsgründen werden der vorherige und der nächste Artikel nur für den aktuellen Iterationsartikel beibehalten. Es geht so:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// Based on source: http://jonskeet.uk/csharp/miscutil/
namespace Generic.Utilities
{
/// <summary>
/// Static class to make creation easier. If possible though, use the extension
/// method in SmartEnumerableExt.
/// </summary>
public static class SmartEnumerable
{
/// <summary>
/// Extension method to make life easier.
/// </summary>
/// <typeparam name="T">Type of enumerable</typeparam>
/// <param name="source">Source enumerable</param>
/// <returns>A new SmartEnumerable of the appropriate type</returns>
public static SmartEnumerable<T> Create<T>(IEnumerable<T> source)
{
return new SmartEnumerable<T>(source);
}
}
/// <summary>
/// Type chaining an IEnumerable<T> to allow the iterating code
/// to detect the first and last entries simply.
/// </summary>
/// <typeparam name="T">Type to iterate over</typeparam>
public class SmartEnumerable<T> : IEnumerable<SmartEnumerable<T>.Entry>
{
/// <summary>
/// Enumerable we proxy to
/// </summary>
readonly IEnumerable<T> enumerable;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="enumerable">Collection to enumerate. Must not be null.</param>
public SmartEnumerable(IEnumerable<T> enumerable)
{
if (enumerable == null)
{
throw new ArgumentNullException("enumerable");
}
this.enumerable = enumerable;
}
/// <summary>
/// Returns an enumeration of Entry objects, each of which knows
/// whether it is the first/last of the enumeration, as well as the
/// current value and next/previous values.
/// </summary>
public IEnumerator<Entry> GetEnumerator()
{
using (IEnumerator<T> enumerator = enumerable.GetEnumerator())
{
if (!enumerator.MoveNext())
{
yield break;
}
bool isFirst = true;
bool isLast = false;
int index = 0;
Entry previous = null;
T current = enumerator.Current;
isLast = !enumerator.MoveNext();
var entry = new Entry(isFirst, isLast, current, index++, previous);
isFirst = false;
previous = entry;
while (!isLast)
{
T next = enumerator.Current;
isLast = !enumerator.MoveNext();
var entry2 = new Entry(isFirst, isLast, next, index++, entry);
entry.SetNext(entry2);
yield return entry;
previous.UnsetLinks();
previous = entry;
entry = entry2;
}
yield return entry;
previous.UnsetLinks();
}
}
/// <summary>
/// Non-generic form of GetEnumerator.
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Represents each entry returned within a collection,
/// containing the value and whether it is the first and/or
/// the last entry in the collection's. enumeration
/// </summary>
public class Entry
{
#region Fields
private readonly bool isFirst;
private readonly bool isLast;
private readonly T value;
private readonly int index;
private Entry previous;
private Entry next = null;
#endregion
#region Properties
/// <summary>
/// The value of the entry.
/// </summary>
public T Value { get { return value; } }
/// <summary>
/// Whether or not this entry is first in the collection's enumeration.
/// </summary>
public bool IsFirst { get { return isFirst; } }
/// <summary>
/// Whether or not this entry is last in the collection's enumeration.
/// </summary>
public bool IsLast { get { return isLast; } }
/// <summary>
/// The 0-based index of this entry (i.e. how many entries have been returned before this one)
/// </summary>
public int Index { get { return index; } }
/// <summary>
/// Returns the previous entry.
/// Only available for the CURRENT entry!
/// </summary>
public Entry Previous { get { return previous; } }
/// <summary>
/// Returns the next entry for the current iterator.
/// Only available for the CURRENT entry!
/// </summary>
public Entry Next { get { return next; } }
#endregion
#region Constructors
internal Entry(bool isFirst, bool isLast, T value, int index, Entry previous)
{
this.isFirst = isFirst;
this.isLast = isLast;
this.value = value;
this.index = index;
this.previous = previous;
}
#endregion
#region Methods
/// <summary>
/// Fix the link to the next item of the IEnumerable
/// </summary>
/// <param name="entry"></param>
internal void SetNext(Entry entry)
{
next = entry;
}
/// <summary>
/// Allow previous and next Entry to be garbage collected by setting them to null
/// </summary>
internal void UnsetLinks()
{
previous = null;
next = null;
}
/// <summary>
/// Returns "(index)value"
/// </summary>
/// <returns></returns>
public override string ToString()
{
return String.Format("({0}){1}", Index, Value);
}
#endregion
}
}
}
Sie können eine speziell dafür entwickelte Erweiterungsmethode erstellen:
public static class EnumerableExtensions {
public static bool IsLast<T>(this List<T> items, T item)
{
if (items.Count == 0)
return false;
T last = items[items.Count - 1];
return item.Equals(last);
}
}
und du kannst es so verwenden:
foreach (Item result in Model.Results)
{
if(Model.Results.IsLast(result))
{
//do something in the code
}
}
Um für jedes Element außer dem letzten Element etwas zu tun, kann ein funktionsbasierter Ansatz verwendet werden.
delegate void DInner ();
....
Dinner inner=delegate
{
inner=delegate
{
// do something additional
}
}
foreach (DataGridViewRow dgr in product_list.Rows)
{
inner()
//do something
}
}
Dieser Ansatz hat offensichtliche Nachteile: weniger Code-Klarheit für komplexere Fälle. Das Anrufen von Delegierten ist möglicherweise nicht sehr effektiv. Die Fehlerbehebung ist möglicherweise nicht ganz einfach. Die helle Seite - Codieren macht Spaß!
Wenn Sie wissen, dass die Anzahl Ihrer Sammlungen nicht sehr langsam ist, würde ich vorschlagen, für Schleifen in trivialen Fällen plain zu verwenden.
Jon Skeet hat vor einiger Zeit einen SmartEnumerable<T>
-Typ erstellt, um genau dieses Problem zu lösen. Sie können die Implementierung hier sehen:
http://codeblog.jonskeet.uk/2007/07/27/smart-enumerations/
Zum Herunterladen: http://www.yoda.arachsys.com/csharp/miscutil/
List<int> ListInt = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int count = ListInt.Count;
int index = 1;
foreach (var item in ListInt)
{
if (index != count)
{
Console.WriteLine("do something at index number " + index);
}
else
{
Console.WriteLine("Foreach loop, this is the last iteration of the loop " + index);
}
index++;
}
//OR
int count = ListInt.Count;
int index = 1;
foreach (var item in ListInt)
{
if (index < count)
{
Console.WriteLine("do something at index number " + index);
}
else
{
Console.WriteLine("Foreach loop, this is the last iteration of the loop " + index);
}
index++;
}
Eine andere Möglichkeit, die ich nicht gepostet habe, ist die Verwendung einer Warteschlange. Es ist analog zu einer Möglichkeit, eine SkipLast () -Methode zu implementieren, ohne mehr als nötig zu iterieren. Auf diese Weise können Sie dies auch für eine beliebige Anzahl der letzten Elemente tun.
public static void ForEachAndKnowIfLast<T>(
this IEnumerable<T> source,
Action<T, bool> a,
int numLastItems = 1)
{
int bufferMax = numLastItems + 1;
var buffer = new Queue<T>(bufferMax);
foreach (T x in source)
{
buffer.Enqueue(x);
if (buffer.Count < bufferMax)
continue; //Until the buffer is full, just add to it.
a(buffer.Dequeue(), false);
}
foreach (T item in buffer)
a(item, true);
}
Um das anzurufen, führen Sie folgende Schritte aus:
Model.Results.ForEachAndKnowIfLast(
(result, isLast) =>
{
//your logic goes here, using isLast to do things differently for last item(s).
});
mit Linq und dem foreach:
foreach (Item result in Model.Results)
{
if (Model.Results.IndexOf(result) == Model.Results.Count - 1) {
// this is the last item
}
}
Speichern Sie einfach den vorherigen Wert und arbeiten Sie damit innerhalb der Schleife. Am Ende ist der 'vorherige' Wert das letzte Element, sodass Sie anders damit umgehen können. Keine Zählung oder spezielle Bibliotheken erforderlich.
bool empty = true;
Item previousItem;
foreach (Item result in Model.Results)
{
if (!empty)
{
// We know this isn't the last item because it came from the previous iteration
handleRegularItem(previousItem);
}
previousItem = result;
empty = false;
}
if (!empty)
{
// We know this is the last item because the loop is finished
handleLastItem(previousItem);
}
So konvertieren Sie foreach
, um auf das letzte Element zu reagieren:
List<int> myList = new List<int>() {1, 2, 3, 4, 5};
Console.WriteLine("foreach version");
{
foreach (var current in myList)
{
Console.WriteLine(current);
}
}
Console.WriteLine("equivalent that reacts to last element");
{
var enumerator = myList.GetEnumerator();
if (enumerator.MoveNext() == true) // Corner case: empty list.
{
while (true)
{
int current = enumerator.Current;
// Handle current element here.
Console.WriteLine(current);
bool ifLastElement = (enumerator.MoveNext() == false);
if (ifLastElement)
{
// Cleanup after last element
Console.WriteLine("[last element]");
break;
}
}
}
enumerator.Dispose();
}
Sie können einfach eine for-Schleife verwenden und es ist nicht notwendig, eine zusätzliche if
im for
-Body hinzuzufügen:
for (int i = 0; i < Model.Results.Count - 1; i++) {
var item = Model.Results[i];
}
Der -1
in der for
-Bedingung sorgt dafür, dass das letzte Element übersprungen wird.