2012-05-24 7 views
11

Ich habe eine IEnumerable<T>. Ich möchte für jeden Gegenstand der Sammlung eine Sache erledigen, mit Ausnahme des letzten Gegenstandes, zu dem ich noch etwas anderes machen möchte. Wie kann ich das ordentlich codieren? In PseudocodeIEnumerable foreach, etwas anderes für das letzte Element tun

foreach (var item in collection) 
{ 
    if (final) 
    { 
     g(item) 
    } 
    else 
    { 
     f(item) 
    } 
} 

Also, wenn mein IEnumerable Enumerable.Range(1,4) wäre, würde ich tun, f (1) f (2) f (3) g (4). NB. Wenn mein IEnumerable die Länge 1 hat, möchte ich g (1).

Mein IEnumerable ist irgendwie beschissen, was Count() so teuer macht wie das ganze Ding zu schleifen.

Antwort

20

Da Sie erwähnen IEnumerable[<T>] (nicht IList[<T>] usw.), können wir nicht auf Zählungen verlassen etc: so würde ich die foreach zu aufzurollen versucht sein:

using(var iter = source.GetEnumerator()) { 
    if(iter.MoveNext()) { 
     T last = iter.Current; 
     while(iter.MoveNext()) { 
      // here, "last" is a non-final value; do something with "last" 
      last = iter.Current; 
     } 
     // here, "last" is the FINAL one; do something else with "last" 
    } 
} 

Notiere die oben ist technisch nur gültig für IEnuemerable<T>; für Nicht-Generika, würden Sie brauchen:

var iter = source.GetEnumerator(); 
using(iter as IDisposable) { 
    if(iter.MoveNext()) { 
     SomeType last = (SomeType) iter.Current; 
     while(iter.MoveNext()) { 
      // here, "last" is a non-final value; do something with "last" 
      last = (SomeType) iter.Current; 
     } 
     // here, "last" is the FINAL one; do something else with "last" 
    } 
} 
+0

Dank Ich habe das in eine Erweiterungsmethode https://gist.github.com/2781446 –

+0

@OJay Nö; das erste Element wird verarbeitet, sobald wir wissen, dass es nicht das * final * -Element ist, also: wenn wir das zweite gelesen haben, dass wir im ersten Kommentar mit 'last' arbeiten, aka dem ** vorherigen ** Element, * nicht * der aktuelle –

+0

'IEnuemerable' sollte' IEnumerable' lesen Ich rate. – Blairg23

1

Wenn Sie dies so effizient machen wollen wie möglich dort keine andere Wahl als effektiv den Strom nicht nur an, sondern auch die „nächste“ oder „Zurück“ suchen So können Sie die Entscheidung, was Sie tun müssen, wenn Sie diese Informationen haben, verschieben. Zum Beispiel T unter der Annahme, ist die Art der Elemente in der Auflistung:

if (collection.Any()) { 
    var seenFirst = false; 
    T prev = default(T); 
    foreach (var current in collection) { 
     if (seenFirst) Foo(prev); 
     seenFirst = true; 
     prev = current; 
    } 
    Bar(prev); 
} 

See it in action.

+0

reverse und tolist sind beide Pufferoperationen ... für eine lange Sequenz könnte das wirklich sehr schmerzhaft sein. –

+0

@MarcGravell: Sicher.Ich dachte, die schnellen und schmutzigen Lösungen zuerst und die guten letzten, aber es stellt sich heraus, die gute ist nicht länger als die Quickies so aus dem Fenster sie gehen. :) – Jon

+0

das ist netter; p –

0

ich es nicht wirklich empfehlen würde, aber ich denke, Sie so etwas wie dieses ...

tun konnte
object m_item = notPartOfListFlag = new object(); 
foreach(var item in enumerator){ 
    if(m_item != notPartOfListFlag) 
    { 
     //do stuff to m_item; 
    } 
    m_item = item; 
} 
//do stuff to last item aka m_item; 

Aber ich würde versuchen, irgendeine Art von Sammlung zu verwenden, die die Position der Elemente in der aussetzt Liste, verwenden Sie dann

if(collection.IndexOf(item) == collection.Count-1) do stuff 
+0

'null' ist ein perfekt gültiger Wert ... –

+0

Das stimmt, gib mir eine Sekunde und ich werde es aktualisieren, um das zu berücksichtigen. – JonC

0

nicht 100% sicher ich mag dieses, aber man könnte die Verwendung von Artikel immer verzögern, bis Sie einen Schritt in die IEnumerable Array verschoben haben, auf diese Weise, wenn Sie bis zum Ende erhalten Sie‘ Ich habe den letzten Eintrag nicht benutzt.

es vermeidet, eine Zählung auf dem Enumerator erzwingen zu müssen.

+1

'null' könnte ein vollkommen gültiger Wert sein ... –

+0

Der Anfangswert muss nicht wirklich null sein, Sie könnten ihn auf alles setzen, was kein gültiger Wert ist, und einfach überprüfen, dass es nicht gleich ist. Oder verwenden Sie eine Flagge, um zu sagen, dass es die erste Schleife ist. – Andy

+0

Es ist möglich, dass keine ungültigen Werte vorhanden sind. – Servy

2

Ähnlich wie Marc Antwort, aber Sie könnten eine Erweiterungsmethode schreiben, um es zu beenden.

public static class LastEnumerator 
{ 
    public static IEnumerable<MetaEnumerableItem<T>> GetLastEnumerable<T>(this IEnumerable<T> blah) 
    { 
     bool isFirst = true; 
     using (var enumerator = blah.GetEnumerator()) 
     { 
      if (enumerator.MoveNext()) 
      { 
       bool isLast; 
       do 
       { 
        var current = enumerator.Current; 
        isLast = !enumerator.MoveNext(); 
        yield return new MetaEnumerableItem<T> 
         { 
          Value = current, 
          IsLast = isLast, 
          IsFirst = isFirst 
         }; 
        isFirst = false; 
       } while (!isLast); 
      } 
     } 

    } 
} 

public class MetaEnumerableItem<T> 
{ 
    public T Value { get; set; } 
    public bool IsLast { get; set; } 
    public bool IsFirst { get; set; } 
} 

es dann rufen Sie wie folgt:

foreach (var row in records.GetLastEnumerable()) 
{ 
    output(row.Value); 
    if(row.IsLast) 
    { 
     outputLastStuff(row.Value); 
    } 
}