Pregunta

Tengo una clase de lista, y me gustaría anular GetEnumerator() para volver a mi propia clase de enumerador. Esta clase enumerador tendría dos propiedades adicionales que se actualizan en la medida se utiliza el enumerador.

Para simplificar (este no es el caso de negocio exacta), digamos que esas propiedades fueron CurrentIndex y RunningTotal.

Podría manejar estas propiedades dentro del bucle foreach manualmente, pero preferiría encapsular esta funcionalidad para su reutilización, y el enumerador parece ser el lugar correcto.

El problema: foreach disimular todo el negocio enumerador, por lo que hay una manera que, dentro de una instrucción foreach, acceder al enumerador actual, de modo que puedo recuperar mis propiedades? O tendría que foreach, utilizar una vieja desagradable bucle while, y manipular el enumerador de mí mismo?

¿Fue útil?

Solución

En sentido estricto, yo diría que si quieres hacer exactamente lo que está diciendo, entonces sí, lo que tendría que llamar GetEnumerator y controlar el empadronador a sí mismo con un bucle while.

Sin saber demasiado acerca de sus necesidades de negocio, usted podría ser capaz de tomar ventaja de una función de repetidor, como algo como esto:

    public static IEnumerable<decimal> IgnoreSmallValues(List<decimal> list)
    {
        decimal runningTotal = 0M;
        foreach (decimal value in list)
        {
            // if the value is less than 1% of the running total, then ignore it
            if (runningTotal == 0M || value >= 0.01M * runningTotal)
            {
                runningTotal += value;
                yield return value;
            }
        }
    }

A continuación, puede hacer esto:

        List<decimal> payments = new List<decimal>() {
            123.45M,
            234.56M,
            .01M,
            345.67M,
            1.23M,
            456.78M
        };

        foreach (decimal largePayment in IgnoreSmallValues(payments))
        {
            // handle the large payments so that I can divert all the small payments to my own bank account.  Mwahaha!
        }

Actualizado:

Ok, así que aquí tiene un seguimiento con lo que hemos llamado mi solución "anzuelo". Ahora, permítanme añadir una advertencia de que en realidad no puedo pensar en una buena razón para hacer algo así, pero su situación puede ser diferente.

La idea es que sólo tiene que crear un objeto "anzuelo" (tipo de referencia) que se pasa a la función de repetidor. La función de repetidor manipula el objeto anzuelo de pesca, y ya que todavía tiene una referencia a él en su código fuera, que tienen visibilidad de lo que está pasando:

    public class FishingHook
    {
        public int Index { get; set; }
        public decimal RunningTotal { get; set; }
        public Func<decimal, bool> Criteria { get; set; }
    }

    public static IEnumerable<decimal> FishingHookIteration(IEnumerable<decimal> list, FishingHook hook)
    {
        hook.Index = 0;
        hook.RunningTotal = 0;
        foreach(decimal value in list)
        {
            // the hook object may define a Criteria delegate that
            // determines whether to skip the current value
            if (hook.Criteria == null || hook.Criteria(value))
            {
                hook.RunningTotal += value;
                yield return value;
                hook.Index++;
            }
        }
    }

Se podría utilizar de esta manera:

        List<decimal> payments = new List<decimal>() {
            123.45M,
            .01M,
            345.67M,
            234.56M,
            1.23M,
            456.78M
        };

        FishingHook hook = new FishingHook();

        decimal min = 0;
        hook.Criteria = x => x > min; // exclude any values that are less than/equal to the defined minimum
        foreach (decimal value in FishingHookIteration(payments, hook))
        {
            // update the minimum
            if (value > min) min = value;

            Console.WriteLine("Index: {0}, Value: {1}, Running Total: {2}", hook.Index, value, hook.RunningTotal);
        }
        // Resultint output is:
        //Index: 0, Value: 123.45, Running Total: 123.45
        //Index: 1, Value: 345.67, Running Total: 469.12
        //Index: 2, Value: 456.78, Running Total: 925.90
        // we've skipped the values .01, 234.56, and 1.23

En esencia, el objeto FishingHook le da un cierto control sobre cómo se ejecuta el iterador. La impresión que tuve de la cuestión era que se necesitaba alguna manera de acceder a los mecanismos internos del iterador de manera que se podía manipular la forma en que itera mientras se encuentra en el medio de la iteración, pero si esto no es el caso, entonces esta solución podría ser excesiva para lo que necesita.

Otros consejos

Con foreach que de hecho no se puede conseguir el empadronador - se puede, sin embargo, tienen el retorno empadronador (yield) una tupla que incluye que los datos; De hecho, probablemente podría utilizar LINQ que lo haga por usted ...

(no pude limpiamente obtener el índice usando LINQ - puede obtener el valor total y la corriente a través de Aggregate, aunque, así que aquí está el enfoque tupla)

using System.Collections;
using System.Collections.Generic;
using System;
class MyTuple
{
    public int Value {get;private set;}
    public int Index { get; private set; }
    public int RunningTotal { get; private set; }
    public MyTuple(int value, int index, int runningTotal)
    {
        Value = value; Index = index; RunningTotal = runningTotal;
    }
    static IEnumerable<MyTuple> SomeMethod(IEnumerable<int> data)
    {
        int index = 0, total = 0;
        foreach (int value in data)
        {
            yield return new MyTuple(value, index++,
                total = total + value);
        }
    }
    static void Main()
    {
        int[] data = { 1, 2, 3 };
        foreach (var tuple in SomeMethod(data))
        {
            Console.WriteLine("{0}: {1} ; {2}", tuple.Index,
                tuple.Value, tuple.RunningTotal);
        }
    }
}

También puede hacer algo como esto de una manera más funcional, dependiendo de sus necesidades. Lo que está pidiendo puede ser entendidas como "comprimir" juntos múltiples secuencias, y luego iterar a través de todos ellos a la vez. Las tres secuencias para el ejemplo que diste serían:

  1. La secuencia de "valor"
  2. El "índice" secuencia
  3. El "Ejecución Total" Secuencia

El siguiente paso sería especificar cada una de estas secuencias por separado:

List<decimal> ValueList
var Indexes = Enumerable.Range(0, ValueList.Count)

El último es más divertido ... los dos métodos que se me ocurren son o bien tener una variable temporal que se utiliza para resumir la secuencia, o para volver a calcular la suma de cada elemento. La segunda es, evidentemente, mucho menos eficiente, prefiero utilizar el temporal:

decimal Sum = 0;
var RunningTotals = ValueList.Select(v => Sum = Sum + v);

El último paso sería la de comprimir estos todos juntos. .Net 4 tendrá la href="http://blogs.msdn.com/ericlippert/archive/2009/05/07/zip-me-up.aspx" rel="nofollow noreferrer"> operador postal , en cuyo caso se vería así:

var ZippedSequence = ValueList.Zip(Indexes, (value, index) => new {value, index}).Zip(RunningTotals, (temp, total) => new {temp.value, temp.index, total});

Esto, obviamente, se pone más ruidosas las cosas más que intenta comprimir juntos.

En el último eslabón, no es de código para implementar la función Zip usted mismo. Realmente es un poco de código simple.

scroll top