我写的程序如下:

  • 找到的所有文件正确的延长,在给定目录
  • Foreach,找出现的所有给予串在那些文件
  • 印每线

我想写这在功能方式,作为一系列的发电机的功能(东西那个电话 yield return 并且只有返回的一个项目的时间延迟装载),因此我的代码阅读这样的:

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

这都是好的,但是我也想要做的就是打印一些统计数据,在结束。事情是这样的:

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

问题是,编写代码在一个纯粹的功能性'的风格如上所述,每个项目被延迟装载。
你只知道有多少文件相匹配,在总额,直至最终foreach循环完成,因为只有一个项目是有史以来 yielded在一个时间代码没有任何地方跟踪的如何许多的事情,它是找到以前。如果你调用皇宫的 matchingLines.Count() 方法,它将重新列举的集合!

我能想到的许多方法来解决这个问题,但他们似乎有点丑陋的。它袭击我的东西,人们就一定能有所做之前,我敢肯定还会有一个很好的设计图案显示了一种最佳做法的方式这样做。

任何想法?欢呼

有帮助吗?

解决方案

与此类似,其他的答案,但考虑一个稍微更加通用的办法...

...为什么不创建一个 装饰 类,可以用现有类型的执行和计算统计数字,因为它传递的其他项目通过。

这里有一个 Counter 类我只是扔在一起-但是你可以创建的变化对其他类型的聚集。

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

你可以创建的三个实例 Counter:

  1. 一个包裹 GetAllFiles() 计算总数的文件;
  2. 一个包裹 GetMatches() 计算数量的匹配的文件;和
  3. 一个包裹 GetMatchingLines() 计算数量的匹配线。

关键用这种方法是,你不层次的多重责任上你现有课程/方法- GetMatchingLines() 方法只能处理的匹配,你就不要求它的轨道统计数据。

澄清 在回应评论 Mitcham:

最后码会看起来像这样的东西:

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

注意,这仍然是一个功能处理方式-使用的变量是 不可改变的 (更多的喜欢 绑定的 比变量),和总体功能没有副作用。

其他提示

我想说你需要封装的进程进入一个'匹配'类中,你的方式捕获的统计数据,因为他们的进展。

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

我会停止存在,因为我应该编码工作的东西,但总的想法是存在的。整个点'纯'功能的程序是没有副作用,而这种类型的静态计算的一个副作用。

我可以想到的两个想法

  1. 通过在一个上下文对象和返回(string+上下文),从你的调查员-纯粹的功能的解决方案

  2. 使用螺纹的地方储存,你的统计数据(CallContext),你可以花哨和支持一叠情况。所以你会像这样的代码.

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

如果你乐意把你的代码倒过来,你可能会感兴趣推皇宫.其基本思想是反向的"拉"型的 IEnumerable<T> 并把它变成一个"推动"的模式与观察员-每一部分的管道有效地推动其数据过去的任何数量的观察员(使用事件处理程序),这通常形成新的部件的管道。这给出了一个非常简单的方法挂钩多聚集到相同数据。

看看 这个博客入口 一些更多的详细信息。我给了一谈对它在伦敦的一段时间前我的 网页的会谈 有几个链接样的代码,在幻灯片、视频等。

这是一个有趣的小项目,但它确实需要一点你的头左右。

我带贝的代码和重它,直到我是内容。有趣的东西。

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

输出为:21,3,3

假设这些职能是自己的,我唯一能想到的被访问者模式,通过在一个抽象的访问功能,叫你回来的时候每件事发生。例如:通过ILineVisitor入GetFileContents(其中我想打破了该文件进行)。ILineVisitor会一样的方法OnVisitLine(String),然后可以实现的ILineVisitor和使它保持适当的统计数据。冲洗和重复与ILineMatchVisitor,IFileVisitor等。或者你可以使用一个单一的IVisitor与OnVisit()方法,它有一个不同的语义在每一种情况。

您的职能将每个需要采取一个访问者,并呼吁它的OnVisit()在适当的时间,这可能似乎令人讨厌,但是至少有访客可以做很多有趣的事情,不仅仅是其他什么你在这里做.事实上,你其实可以避免编写GetMatchingLines通过一个访客检查的匹配在OnVisitLine(String线)的成GetFileContents.

这是一项丑陋的东西你已经考虑?

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top