Frage

Ich schreibe ein Programm wie folgt:

  • Alle Dateien mit der richtigen Erweiterung in einem bestimmten Verzeichnis
  • Foreach, finden Sie alle Vorkommen einer bestimmten Zeichenfolge in diesen Dateien
  • Drucken jeder Zeile

Ich mag diese in einer funktionalen Weise schreiben, als eine Reihe von Generatorfunktionen (Dinge, die yield return rufen und zurück nur ein Element in einer Zeit träge belastet), so würde mein Code wie folgt lauten:

IEnumerable<string> allFiles = GetAllFiles();
IEnumerable<string> matchingFiles = GetMatches( "*.txt", allFiles );
IEnumerable<string> contents = GetFileContents( matchingFiles );
IEnumerable<string> matchingLines = GetMatchingLines( contents );

foreach( var lineText in matchingLines )
  Console.WriteLine( "Found: " + lineText );

Das ist alles in Ordnung, aber was würde ich auch tun mag, ist, drucken Statistiken am Ende. So etwas wie folgt aus:

Found 233 matches in 150 matching files. Scanned 3,297 total files in 5.72s

Das Problem ist, den Code in einem ‚reinen funktionalen‘ Schreibstil wie oben, wird jedes Element lazily geladen.
Sie wissen nur, wie viele Dateien insgesamt übereinstimmen, bis die endgültige foreach-Schleife abgeschlossen ist, und weil nur ein Element jemals zu einem Zeitpunkt yielded wird, wird der Code hat keinen Platz, um zu verfolgen, wie viele Dinge, die es vorher gefunden hat. Wenn Sie LINQ matchingLines.Count() Methode aufrufen, wird die Sammlung neu auflisten!

kann ich von vielen Möglichkeiten denken, dieses Problem zu lösen, aber sie alle scheinen etwas hässlich zu sein. Es kommt mir etwas, das getan werden gebunden Menschen vor haben, und ich bin sicher, es werde ein schönes Design-Muster sein, die eine Best-Practice-Art und Weise zeigt, dies zu tun.

Irgendwelche Ideen? Prost

War es hilfreich?

Lösung

In ähnlicher Weise auf andere Antworten, aber einen etwas allgemeineren Ansatz ...

... warum nicht ein Decorator Klasse erstellen, die eine vorhandene IEnumerable Implementierung und berechnen Sie die Statistik wickeln kann, wie es andere Elemente durchläuft.

Hier ist eine Counter Klasse I zusammen gerade warf -. Aber man kann auch Variationen für andere Arten der Aggregation erstellen

public class Counter<T> : IEnumerable<T>
{
    public int Count { get; private set; }

    public Counter(IEnumerable<T> source)
    {
        mSource = source;
        Count = 0;
    }

    public IEnumerator<T> GetEnumerator()
    {
        foreach (var T in mSource)
        {
            Count++;
            yield return T;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        foreach (var T in mSource)
        {
            Count++;
            yield return T;
        }
    }

    private IEnumerable<T> mSource;
}

Sie könnten drei Instanzen von Counter erstellen:

  1. Ein GetAllFiles() wickeln die Gesamtzahl der Dateien zu zählen;
  2. Ein GetMatches() wickeln die Anzahl der passenden Dateien zu zählen; und
  3. One GetMatchingLines() Zählen der Anzahl von Anpassungsleitungen wickeln.

Der Schlüssel bei diesem Ansatz ist, dass Sie nicht mehrere Aufgaben auf Ihre vorhandenen Klassen / Methoden sind Schichtung -. Die GetMatchingLines() Methode nur nimmt die passende, du bist es nicht zu fragen und Statistiken zu verfolgen

Klarstellung in Reaktion auf einen Kommentar von Mitcham:

Der letzte Code würde wie folgt aussehen:

var files = new Counter<string>( GetAllFiles());
var matchingFiles = new Counter<string>(GetMatches( "*.txt", files ));
var contents = GetFileContents( matchingFiles );
var linesFound = new Counter<string>(GetMatchingLines( contents ));

foreach( var lineText in linesFound )
    Console.WriteLine( "Found: " + lineText );

string message 
    = String.Format( 
        "Found {0} matches in {1} matching files. Scanned {2} files",
        linesFound.Count,
        matchingFiles.Count,
        files.Count);
Console.WriteLine(message);

Beachten Sie, dass dies immer noch ein funktioneller Ansatz - die verwendeten Variablen sind unveränderlich (mehr wie Bindungen als Variablen) und die Gesamtfunktion hat keine Nebenwirkungen <. / p>

Andere Tipps

Ich würde sagen, dass Sie den Prozess in eine ‚Matcher‘ Klasse kapseln müssen, in denen Ihre Methoden capture Statistiken, wie sie Fortschritte.

public class Matcher
{
  private int totalFileCount;
  private int matchedCount;
  private DateTime start;
  private int lineCount;
  private DateTime stop;

  public IEnumerable<string> Match()
  {
     return GetMatchedFiles();
     System.Console.WriteLine(string.Format(
       "Found {0} matches in {1} matching files." + 
       " {2} total files scanned in {3}.", 
       lineCount, matchedCount, 
       totalFileCount, (stop-start).ToString());
  }

  private IEnumerable<File> GetMatchedFiles(string pattern)
  {
     foreach(File file in SomeFileRetrievalMethod())
     {
        totalFileCount++;
        if (MatchPattern(pattern,file.FileName))
        {
          matchedCount++;
          yield return file;
        }
     }
  }
}

Ich werde aufhören, da ich angeblich Codierung Arbeit Sachen werden, aber die allgemeine Idee ist da. Der gesamte Punkt von ‚reinem‘ Funktionsprogramm ist keine Nebenwirkungen haben, und diese Art der Statik-Berechnung ist ein Nebeneffekt.

Ich kann mich zwei Ideen

  1. Der Pass in einem Kontextobjekt und zurück (string + Kontext) von Ihrem Enumeratoren - die rein funktionale Lösung

  2. Verwendung Thread-Lokalspeicher für Sie Statistiken ( Callcontext ), können Sie Lust sein und einen Stapel von Kontexten unterstützen. so würden Sie Code wie diesen haben.

    using (var stats = DirStats.Create())
    {
        IEnumerable<string> allFiles = GetAllFiles();
        IEnumerable<string> matchingFiles = GetMatches( "*.txt", allFiles );
        IEnumerable<string> contents = GetFileContents( matchingFiles );
        stats.Print()
        IEnumerable<string> matchingLines = GetMatchingLines( contents );
        stats.Print();
    } 
    

Wenn Sie zufrieden sind Sie den Code auf den Kopf stellen, könnten Sie in Push-LINQ interessiert. Die Grundidee ist es, den „Pull“ -Modell von IEnumerable<T> zu umkehren und macht es zu einem „Push“ -Modell mit Beobachtern - jeder Teil der Pipeline seiner Daten über eine beliebige Anzahl von Beobachtern effektiv drückt (mit Event-Handler), die typischerweise neue Teile bilden, die Pipeline. Daraus ergibt sich eine wirklich einfache Möglichkeit, mehrere Aggregate auf die gleichen Daten zu anschließen.

Siehe dieser Blog-Eintrag für einige weitere Details. Ich habe vor einer Weile auf sie in London einen Vortrag - mein Seite der Gespräche ein paar Links für Probe hat Code, der Schlitten Deck, Video etc.

Es ist ein lustiges, kleines Projekt, aber es ein bisschen immer Ihren Kopf dauert herum.

Ich nahm Bevans Code und Refactoring es herum, bis ich zufrieden war. Spaßiger Teil.

public class Counter
{
    public int Count { get; set; }
}

public static class CounterExtensions
{
    public static IEnumerable<T> ObserveCount<T>
      (this IEnumerable<T> source, Counter count)
    {
        foreach (T t in source)
        {
            count.Count++;
            yield return t;
        }
    }

    public static IEnumerable<T> ObserveCount<T>
      (this IEnumerable<T> source, IList<Counter> counters)
    {
        Counter c = new Counter();
        counters.Add(c);
        return source.ObserveCount(c);
    }
}


public static class CounterTest
{
    public static void Test1()
    {
        IList<Counter> counters = new List<Counter>();
  //
        IEnumerable<int> step1 =
            Enumerable.Range(0, 100).ObserveCount(counters);
  //
        IEnumerable<int> step2 =
            step1.Where(i => i % 10 == 0).ObserveCount(counters);
  //
        IEnumerable<int> step3 =
            step2.Take(3).ObserveCount(counters);
  //
        step3.ToList();
        foreach (Counter c in counters)
        {
            Console.WriteLine(c.Count);
        }
    }
}

Ausgabe wie erwartet: 21, 3, 3

diese Funktionen sind Ihre eigenen, das einzige, was Angenommen, ich denken kann, ist das Besuchermuster, in einer abstrakten Besucher Funktion übergeben, die Sie zurückrufen, wenn jedes Ding passiert. Zum Beispiel: eine ILineVisitor in GetFileContents passieren (was ich aufbricht die Datei in Zeilen vorausgesetzt). ILineVisitor wäre eine Methode, wie OnVisitLine (String Linie) haben, können Sie dann die ILineVisitor implementieren und die entsprechenden Statistiken machen zu halten. Spülen und wiederholen mit einem ILineMatchVisitor, IFileVisitor usw. Sie können auch eine einzelne IVisitor mit einem OnVisit () Methode verwendet werden, die eine andere semantische jeweils hat.

Ihre Funktionen würde jeder braucht ein Besucher zu nehmen, und nennen es OnVisit () zu gegebener Zeit, die lästig erscheinen, aber zumindest konnte der Besucher benutzt werden viele interessante Dinge zu tun, die nicht genau das, was Sie‘ re hier zu tun. In der Tat könnte man vermeiden, tatsächlich GetMatchingLines, indem einen Besucher zu schreiben, die in OnVisitLine (String Linie) in GetFileContents für das Spiel überprüft.

Ist das eine der hässlichen Dinge, die Sie würden bereits in Betracht gezogen?

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