Question

J'ai une classe List et j'aimerais remplacer GetEnumerator() pour renvoyer ma propre classe Enumerator.Cette classe Enumerator aurait deux propriétés supplémentaires qui seraient mises à jour à mesure que l'Enumerator est utilisé.

Pour simplifier (ce n'est pas une analyse de rentabilisation exacte), disons que ces propriétés étaient CurrentIndex et RunningTotal.

Je pourrais gérer manuellement ces propriétés dans la boucle foreach, mais je préfère encapsuler cette fonctionnalité pour la réutiliser, et l'énumérateur semble être le bon endroit.

Le problème: pour chaque cache toutes les activités de l'énumérateur. Existe-t-il donc un moyen, dans une instruction foreach, d'accéder à l'énumérateur actuel afin que je puisse récupérer mes propriétés ?Ou devrais-je foreach, utiliser une vieille boucle while et manipuler moi-même l'énumérateur ?

Était-ce utile?

La solution

À proprement parler, je dirais que si vous voulez faire exactement ce que vous dites, alors oui, vous devrez appeler GetEnumerator et contrôler vous-même l'énumérateur avec une boucle while.

Sans trop en savoir sur les besoins de votre entreprise, vous pourrez peut-être profiter d'une fonction d'itérateur, comme quelque chose comme ceci :

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

Ensuite vous pouvez faire ceci :

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

Mis à jour:

Ok, voici donc un suivi de ce que j'ai appelé ma solution "hameçon".Maintenant, permettez-moi d'ajouter un avertissement selon lequel je ne trouve pas vraiment de bonne raison de faire quelque chose de cette façon, mais votre situation peut différer.

L'idée est que vous créez simplement un objet "hameçon" (type référence) que vous transmettez à votre fonction itérateur.La fonction itérateur manipule votre objet hameçon, et comme vous y avez toujours une référence dans votre code extérieur, vous avez une visibilité sur ce qui se passe :

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

Vous l'utiliseriez comme ceci :

        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

Essentiellement, l'objet FishingHook vous donne un certain contrôle sur la façon dont l'itérateur s'exécute.L'impression que j'ai eue de la question était que vous aviez besoin d'un moyen d'accéder au fonctionnement interne de l'itérateur afin de pouvoir manipuler la façon dont il itère pendant que vous êtes en cours d'itération, mais si ce n'est pas le cas, alors cette solution pourrait soyez exagéré pour ce dont vous avez besoin.

Autres conseils

Avec foreach vous pouvez en effet pas obtenir le recenseur - vous pouvez, toutefois le retour de recenseur (de yield) un tuple comprend que les données; en fait, vous pouvez probablement utiliser LINQ pour le faire pour vous ...

(je ne pouvais pas proprement obtenir l'indice LINQ - peut obtenir la valeur totale et le courant via Aggregate, bien, voici donc l'approche 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);
        }
    }
}

Vous pouvez aussi faire quelque chose comme ceci d'une manière plus fonctionnelle, en fonction de vos besoins. Ce que vous demandez peut être bien comme « zipper » séquences ensemble multiples, et itérer puis à travers eux à la fois. Les trois séquences pour l'exemple que vous avez données seraient:

  1. La séquence "valeur"
  2. La séquence "index"
  3. La séquence "Exécution totale"

L'étape suivante consiste à spécifier chacune de ces séquences séparément:

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

Le dernier est plus amusant ... les deux méthodes que je peux penser sont soit une variable temporaire utilisée pour résumer la séquence, ou pour recalculer la somme pour chaque élément. Le second est évidemment beaucoup moins performant, je préfère utiliser le temporaire:

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

La dernière étape serait de zip ces tous ensemble. .Net 4 aura le opérateur postal construit , auquel cas il ressemblera à ceci:

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

Cela devient évidemment plus bruyants plus les choses que vous essayez de zip ensemble.

Dans le dernier lien, il est source pour la mise en œuvre de la fonction Zip vous. Il est vraiment un simple petit peu de code.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top