Domanda

Ho una classe List, e vorrei ignorare GetEnumerator() per tornare mia classe Enumerator. Questa classe Enumerator avrebbe due proprietà aggiuntive che sarebbero stati aggiornati come viene utilizzata l'enumerazione.

Per semplicità (questo non è esattamente il business case), diciamo che queste proprietà erano CurrentIndex e RunningTotal.

ho potuto gestire queste proprietà all'interno del ciclo foreach manualmente, ma avrei preferito incapsulare questa funzionalità per il riutilizzo e l'Enumerator sembra essere il posto giusto.

Il problema: foreach nasconde tutti gli affari Enumeratore, così c'è un modo per, all'interno di un'istruzione foreach, accedere al enumeratore corrente in modo da poter recuperare le mie proprietà? Oppure avrei dovuto foreach, utilizzare un grosso vecchio ciclo while, e manipolare l'enumeratore me stesso?

È stato utile?

Soluzione

A rigor di termini, vorrei dire che se si vuole fare esattamente quello che stai dicendo, allora sì, si avrebbe bisogno di chiamare GetEnumerator e controllare l'enumeratore con un ciclo while.

Senza sapere troppo su vostra esigenza di business, si potrebbe essere in grado di approfittare di una funzione iteratore, come qualcosa di simile a questo:

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

Poi si può fare questo:

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

Aggiornamento:

Ok, quindi ecco un follow-up di quello che ho chiamavo la mia soluzione "amo da pesca". Ora, permettetemi di aggiungere un disclaimer che non posso davvero pensare a un buon motivo per fare qualcosa in questo modo, ma la situazione potrebbe essere diversa.

L'idea è che è sufficiente creare un oggetto "pesca gancio" (tipo di riferimento) che si passa alla funzione iteratore. La funzione iteratore manipola l'oggetto gancio di pesca, e dal momento che avete ancora un riferimento ad esso nel codice al di fuori, si ha visibilità su quello che sta succedendo:

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

Si potrebbe utilizzare in questo modo:

        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

In sostanza, l'oggetto FishingHook ti dà un po 'di controllo su come l'iteratore viene eseguito. L'impressione che ho avuto dalla questione era che avevi bisogno di un modo per accedere ai meccanismi interni del iteratore in modo da poter manipolare come si itera mentre si è nel bel mezzo di iterazione, ma se questo non è il caso, allora questa soluzione potrebbe essere eccessivo per quello che ti serve.

Altri suggerimenti

Con foreach si davvero non è possibile ottenere l'enumeratore - si potrebbe, tuttavia, avere il ritorno enumeratore (yield) una tupla che include che i dati; in effetti, si potrebbe forse usare LINQ to farlo per voi ...

(non ho potuto in modo pulito ottenere l'indice utilizzando LINQ - può ottenere il valore totale e corrente tramite Aggregate, anche se, quindi ecco l'approccio 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);
        }
    }
}

Si può anche fare qualcosa di simile in un modo più funzionale, a seconda delle vostre esigenze. Quello che si sta chiedendo se può essere come "zippare" insieme più sequenze, e poi scorrendo tutti in una volta. Le tre sequenze per l'esempio che ha dato potrebbe essere:

  1. La sequenza di "valore"
  2. Il "indice" sequenza
  3. Il "totale parziale" Sequenza

Il passo successivo sarebbe quello di indicare ciascuna di queste sequenze separatamente:

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

L'ultimo è più divertente ... i due metodi mi vengono in mente sono di avere sia una variabile temporanea utilizzata per riassumere la sequenza, o per ricalcolare la somma per ogni elemento. Il secondo è, ovviamente, molto meno performante, avrei preferito utilizzare la temporanea:

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

L'ultimo passo sarebbe quello di comprimere questi tutti insieme. .Net 4 avrà la operatore postale costruita nel , nel qual caso sarà simile a questa:

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

Questo diventa ovviamente più rumorose più le cose si tenta di comprimere insieme.

Nel l'ultimo anello, non v'è fonte per l'implementazione della funzione Zip da soli. E 'davvero un po' semplice di codice.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top