是否可以缓存IEnumerable延迟评估结果?

时间:2016-09-19 14:41:53

标签: c# ienumerable lazy-evaluation

我正在研究一种查找配置文件路径的方法。这需要进行两次传递:首先查找任何现有的配置文件,然后返回并找到第一个可写路径。

虽然我的特殊情况有些过分,但这让我想到:是否有可能同时进行懒惰评估并防止多次枚举?

为了说明我的意思,请考虑以下代码:

public IEnumerable<string> GetPaths()
{
    Console.WriteLine("GetPaths() Returning 'one'");
    yield return "one";
    Console.WriteLine("GetPaths() Returning 'two'");
    yield return "two";
    Console.WriteLine("GetPaths() Returning 'three'");
    yield return "three";
}

public bool IsWritable(string path) => false; // testing only 

如果我跑:

var paths = GetPaths();
Console.WriteLine("Searching for existing file..");
foreach (var path in paths)
{
    if (File.Exists(path))
    {
        Console.WriteLine($"Found existing file '{path}'");
    }
}

Console.WriteLine("Searching for a writable path..");
foreach (var path in paths.Reverse()) // NOTE: paths enumarated twice
{
    if (IsWritable(path))
    {
        Console.WriteLine($"Found writable path '{path}'");
    }
}

Console.WriteLine("No paths found");

如果文件'one'存在,我们得到:

Searching for existing file..
Returning 'one'
Found existing file 'one'

但是,如果没有文件,我们得到:

Searching for existing file..
Returning 'one'
Returning 'two'
Returning 'three'
Searching for a writable path..
Returning 'one'
Returning 'two'
Returning 'three'
No paths found

(我们浪费地枚举GetPaths()两次的结果)

一个简单的解决方法是将第一行更改为:

var paths = GetPaths().ToList();

但是,这意味着即使文件one存在,输出也将是:

Returning 'one'
Returning 'two'
Returning 'three'
Searching for existing file..
Found existing file 'one'

(如我们不必要地枚举列表的其余部分)

是否有(内置)方法来获取延迟枚举并仍然阻止多次枚举?

换句话说,'one'存在时所需的输出是:

Searching for existing file..
Returning 'one'
Found existing file 'one'

如果没有文件:

Searching for existing file..
Returning 'one'
Returning 'two'
Returning 'three'
Searching for a writable path..
No paths found

3 个答案:

答案 0 :(得分:1)

这不是内置于.NET的意义,而是“现成的”。

使用Nuget安装morelinqpackage)。

然后添加

using MoreLinq.Experimental;

将示例更改为:

var paths = GetPaths().Memoize();

它将产生所需的输出。

请注意,morelinq确实认为Memoize是“实验性的” FWIW。

答案 1 :(得分:0)

是的。如果您的Lazy调用返回的Enumerator从基础数据提供程序的同一实例依次获取其数据,并且该提供程序跟踪它产生的每个项目,那么Enumerator的所有实例实际上都是从数据提供程序的同一实例中提取数据

考虑一个Enumerator来从文件中返回行;如果该枚举器与该文件的所有其他枚举器共享相同的Stream实例,那么您将获得您所说明的行为。

在伪代码中

static System.IO.FileStream fs;
private static void GetFileStream()
{
    if(fs == null) fs = System.IO.File.Open(....);
    return fs;
}

public Enumarable<string> GetLines()
{
    // return an enumerator that uses the result of GetFileStream() and 
    // advances the file pointer. All Enumerables returned by this method
    // will return unique lines from the same file
}

答案 2 :(得分:0)

我可能错过了一些内容,但为什么不使用System.Linq.Enumerable的FirstOrDefault扩展方法?

    public static bool IsWritable(string path)
    {
        Func<string, bool> canBeWrittenTo = p => true;// (details omitted)
        return System.IO.File.Exists(path) && canBeWrittenTo(path);
    }

然后:

        var firstWritablePath = GetPaths().FirstOrDefault(path => IsWritable(path));
        if (firstWritablePath != null)
        {
            // Found existing file...
        }
        else
        {
            // No paths found...
        }