Pergunta

Eu tenho uma classe List, e eu gostaria de substituir GetEnumerator() para retornar a minha própria classe Enumerator. Esta classe Enumerator teria duas propriedades adicionais que seriam atualizados como o Enumerator é usado.

Para simplificar (este não é o caso de negócio exato), digamos que essas propriedades foram CurrentIndex e RunningTotal.

Eu poderia gerir estas propriedades dentro do loop foreach manualmente, mas eu preferiria encapsular essa funcionalidade para reutilização, ea Enumerator parece ser o lugar certo.

O problema: foreach peles todo o negócio Enumerator, assim há uma maneira de, em uma instrução foreach, o acesso a corrente Enumerator para que eu possa recuperar minhas propriedades? Ou eu teria que foreach, use um velho desagradável enquanto loop, e manipular o Enumerator me?

Foi útil?

Solução

Estritamente falando, eu diria que, se você quer fazer exatamente o que você está dizendo, então sim, você precisa chamar GetEnumerator e controlar o recenseador-se com um tempo loop.

Sem saber muito sobre suas necessidades de negócios, você pode ser capaz de tirar proveito de uma função de iterador, como algo parecido com isto:

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

Em seguida, você pode fazer isso:

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

Atualizado:

Ok, então aqui está um follow-up com o que tenho chamado a minha solução "anzol". Agora, deixe-me acrescentar um aviso de que eu realmente não posso pensar em uma boa razão para fazer algo assim, mas sua situação pode ser diferente.

A idéia é que você simplesmente criar um "anzol" objeto (tipo de referência) que você passa para o seu iterador função. A função iterator manipula o objeto gancho de pesca, e uma vez que você ainda tem uma referência a ele em sua parte externa código, você tem visibilidade sobre o que está acontecendo:

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

Você iria utilizá-lo como este:

        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

Essencialmente, o objeto FishingHook dá-lhe algum controle sobre como as executa iterador. A impressão que tive da questão era que você precisava de alguma maneira para acessar o funcionamento interno do iterador para que você possa manipular como ele itera enquanto você estiver no meio de iteração, mas se isso não for o caso, então esta solução poder ser um exagero para o que você precisa.

Outras dicas

Com foreach você na verdade não pode obter o recenseador - você poderia, no entanto, ter o retorno recenseador (yield) uma tupla que inclui que os dados; na verdade, você provavelmente poderia usar LINQ para fazer isso por você ...

(eu não poderia limpa obter o índice usando LINQ - pode obter o total e valor atual via Aggregate, embora, por isso aqui é a abordagem tuple)

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

Você também pode fazer algo parecido com isso de uma forma mais funcional, dependendo de suas necessidades. O que você está pedindo pode ser pensada como "zipar" juntos várias seqüências, e depois iteração através de todos eles ao mesmo tempo. As três sequências para o exemplo que você deu seria:

  1. A seqüência de "valor"
  2. A seqüência de "index"
  3. O "Running Total" Sequence

O próximo passo seria a de especificar cada uma dessas seqüências separadamente:

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

A última é mais divertido ... os dois métodos que posso pensar são: ou tem uma variável temporária usada para resumir a sequência, ou para recalcular a soma para cada item. A segunda é, obviamente, muito menos eficaz, eu prefiro usar o temporária:

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

O último passo seria fechar todos estes juntos. .Net 4 terá a operador postal construído em , caso em que será parecido com este:

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

Isto, obviamente, fica mais ruidoso quanto mais as coisas que você tenta zip juntos.

No último elo, não há fonte para implementar a função Zip-se. É realmente um pouco simples de código.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top