2012-05-24 12 views
11

Tengo IEnumerable<T>. Quiero hacer una cosa para cada elemento de la colección, excepto el último elemento, al que quiero hacer otra cosa. ¿Cómo puedo codificar esto prolijamente? En PseudocódigoIEnumerable foreach, haga algo diferente para el último elemento

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

Así que si mi IEnumerable eran Enumerable.Range(1,4) lo haría f (1) f (2) f (3) g (4). NÓTESE BIEN. Si mi IEnumerable tiene la longitud 1, quiero g (1).

Mi IEnumerable es un poco malo, lo que hace que Count() sea tan caro como hacer un bucle sobre todo el asunto.

Respuesta

20

Ya que mencionas IEnumerable[<T>] (no IList[<T>] etc), no podemos confiar en los recuentos etc: por lo que estaría tentado a desenrollar el foreach:

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" 
    } 
} 

cuenta que lo anterior es técnicamente sólo es válida para IEnuemerable<T>; para no genérico, lo que se necesita:

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

Gracias pongo que, en un método de extensión https://gist.github.com/2781446 –

+0

@OJay nop; el primer elemento se procesa una vez que sabemos que no es el elemento * final *, así que: cuando hayamos leído el segundo, tenga en cuenta que en el primer comentario trabajamos con 'last', también conocido como elemento ** previo **, * no * el actual –

+0

'IEnuemerable' debe leer' IEnumerable' Supongo. – Blairg23

1

Si quieres hacer esto lo más eficientemente posible que no hay otra opción que buscar con eficacia no sólo en la actual, sino también el "siguiente" o "anterior" elemento, por lo que puede diferir la decisión de qué hacer después de tener esa información. Por ejemplo, suponiendo T es el tipo de elementos de la colección:

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 y tolist son operaciones de almacenamiento en búfer ... durante una secuencia larga, esto podría ser realmente muy doloroso. –

+0

@MarcGravell: Claro.Pensé en dar las soluciones rápidas y sucias primero y la buena al final, pero resulta que la buena no es más larga que la de los rápidos, así que salgan por la ventana. :) – Jon

+0

que es más agradable; p –

0

Realmente no lo recomiendo, pero supongo que se podría hacer algo como esto ...

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; 

Pero me gustaría tratar de utilizar algún tipo de colección que expone la posición de los elementos de la lista, a continuación, utilizar

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

'null' es un valor perfectamente válido ... –

+0

Es cierto, dame un segundo y lo actualizaré para dar cuenta de eso. – JonC

0

100% seguro de que me gusta esto, pero siempre se puede retrasar el uso del artículo hasta que haya movido un paso a la matriz IEnumerable, de esa manera cuando llegue al final' No he usado la última entrada.

evita tener que forzar un recuento en el enumerador.

object item = null; 
foreach (var a in items) 
{ 
    // if item is set then we can use it. 
    if (item != null) 
    { 
     // not final item 
     f(item); 
    } 
    item = a; 
} 

// final item. 
g(item); 
+1

'null' podría ser un valor perfectamente válido ... –

+0

El valor inicial en realidad no tiene que ser nulo, puede establecerlo en cualquier cosa que no sea un valor válido y simplemente verificar que no sea igual a eso. O usa una bandera para decir que es el primer ciclo. – Andy

+0

Es posible que no haya valores inválidos. – Servy

2

Parecido a la respuesta de Marc, pero podría escribir un método de extensión para concluirlo.

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; } 
} 

Entonces llamarlo así:

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