Frage

Ich habe eine List-Klasse und möchte diese überschreiben GetEnumerator() um meine eigene Enumerator-Klasse zurückzugeben.Diese Enumerator-Klasse verfügt über zwei zusätzliche Eigenschaften, die bei Verwendung des Enumerators aktualisiert werden.

Nehmen wir der Einfachheit halber (dies ist nicht der genaue Geschäftsfall) an, dass diese Eigenschaften vorhanden waren CurrentIndex Und RunningTotal.

Ich könnte diese Eigenschaften innerhalb der foreach-Schleife manuell verwalten, aber ich würde diese Funktionalität lieber zur Wiederverwendung kapseln, und der Enumerator scheint dafür der richtige Ort zu sein.

Das Problem: für jede verbirgt das gesamte Enumerator-Geschäft. Gibt es also eine Möglichkeit, innerhalb einer foreach-Anweisung auf den aktuellen Enumerator zuzugreifen, damit ich meine Eigenschaften abrufen kann?Oder müsste ich foreachen, eine fiese alte While-Schleife verwenden und den Enumerator selbst manipulieren?

War es hilfreich?

Lösung

Genau genommen, würde ich sagen, dass, wenn Sie genau das tun wollen, was Sie sagen, dann ja, Sie müssen GetEnumerator anrufen und steuern der enumerator, dich mit einer while-Schleife.

Ohne zu viel wissen über Ihre Geschäftsanforderungen, könnten Sie in der Lage sein, die Vorteile einer Iterators Funktion zu übernehmen, wie etwa wie folgt:

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

Dann können Sie dies tun:

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

Aktualisiert:

Ok, also hier ist ein Follow-up mit dem, was ich meine „Angelhaken“ Lösung bezeichnet habe. Nun lassen Sie mich einen Haftungsausschluss hinzufügen, dass ich nicht wirklich von einem guten Grund denken kann, etwas so zu tun, aber Ihre Situation kann unterschiedlich sein.

Die Idee ist, dass man einfach ein „Angelhaken“ Objekt erstellen (Referenztyp), dass Sie Ihre Iterator-Funktion übergeben. Der Iterator Funktion manipuliert den Angelhaken Objekt, und da Sie noch einen Verweis auf sie in Ihrem Code außerhalb haben, haben Sie Einblick in, was los ist:

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

Sie würde es so nutzen:

        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

Im Wesentlichen das FishingHook Objekt gibt Ihnen eine gewisse Kontrolle darüber, wie der Iterator ausführt. Der Eindruck, den ich von der Frage bekam, war, dass Sie irgendeine Art und Weise erforderlich, um die inneren Abläufe des Iterators zugreifen, so dass Sie manipulieren könnten, wie es iteriert, während Sie in der Mitte des Iterieren sind, aber wenn dies nicht der Fall ist, dann ist diese Lösung könnte sein Overkill für das, was Sie brauchen.

Andere Tipps

Mit foreach Sie kann in der Tat nicht den Enumerator bekommen - man könnte, hat jedoch den enumerator return (yield) ein Tupel, dass enthält , dass Daten; in der Tat könnte man wahrscheinlich LINQ verwenden Sie es für Sie zu tun ...

(ich konnte nicht sauber erhält den Index mit LINQ - kann die Gesamt und die aktuellen Wert über Aggregate bekommen, obwohl, also hier ist der Tupel-Ansatz)

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

Abhängig von Ihren Anforderungen können Sie so etwas auch auf funktionalere Weise tun.Was Sie fragen, kann darin bestehen, mehrere Sequenzen „zusammenzupacken“ und sie dann alle auf einmal zu durchlaufen.Die drei Sequenzen für das von Ihnen angegebene Beispiel wären:

  1. Die „Wert“-Sequenz
  2. Die „Index“-Sequenz
  3. Die „laufende Summe“-Sequenz

Der nächste Schritt wäre, jede dieser Sequenzen separat anzugeben:

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

Letzteres macht mehr Spaß...Die beiden Methoden, die mir einfallen, bestehen darin, entweder eine temporäre Variable zum Summieren der Sequenz zu verwenden oder die Summe für jedes Element neu zu berechnen.Der zweite ist offensichtlich viel weniger performant, ich würde lieber den temporären verwenden:

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

Der letzte Schritt wäre, diese alle zusammenzupacken..Net 4 wird das haben Integrierter Zip-Operator, in diesem Fall sieht es so aus:

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

Dies wird natürlich umso lauter, je mehr Dinge Sie zusammenzupacken versuchen.

Im letzten Link finden Sie eine Quelle für die Implementierung der Zip-Funktion selbst.Es ist wirklich ein einfacher kleiner Code.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top