C# 如何使用LINQ使C‘grep’更实用?

C# 如何使用LINQ使C‘grep’更实用?,c#,linq,c#-3.0,functional-programming,C#,Linq,C# 3.0,Functional Programming,我有一个方法,它使用可枚举的搜索字符串跨文件执行简单的“grep”。实际上,我正在做一个非常简单的查找所有引用的过程 IEnumerable<string> searchStrings = GetSearchStrings(); IEnumerable<string> filesToLookIn = GetFiles(); MultiMap<string, string> references = new MultiMap<string, string&

我有一个方法,它使用可枚举的搜索字符串跨文件执行简单的“grep”。实际上,我正在做一个非常简单的查找所有引用的过程

IEnumerable<string> searchStrings = GetSearchStrings();
IEnumerable<string> filesToLookIn = GetFiles();
MultiMap<string, string> references = new MultiMap<string, string>();

foreach( string fileName in filesToLookIn )
{
    foreach( string line in File.ReadAllLines( fileName ) )
    {
        foreach( string searchString in searchStrings )
        {
            if( line.Contains( searchString ) )
            {
                references.AddIfNew( searchString, fileName );
            }
        }
    }
}
另一个希望这次保留文件名:

var filesWithLines =
    filesToLookIn
        .Select(f => new { FileName = f, Lines = File.ReadAllLines(f) });

var matchingSearchStrings =
    searchStrings
        .Where(ss => filesWithLines.Any(
                         fwl => fwl.Lines.Any(l => l.Contains(ss))));
但我似乎仍然丢失了我需要的信息

也许我只是从错误的角度来看待这个问题?从性能的角度来看,循环的执行顺序应该与原始示例大致相同


关于如何在更紧凑的函数表示中实现这一点,有什么想法吗?

我将使用FindFile FindFirstFileEx、FindNextFile等API调用在文件中查找您正在搜索的术语。它可能比你一行一行地阅读要快

但是,如果这对你不起作用,你应该考虑创建一个IQueDaby实现,它将读取文件中的行并在读取它们时产生它们,而不是将它们全部读入数组。然后,您可以查询每个字符串,并仅在需要时获取下一个字符串

这会为你节省很多时间

请注意,在.NET 4.0中,许多从文件或搜索文件返回行的IO API将返回IEnumerable实现,这些实现与上面提到的完全相同,因为它将搜索目录/文件,并在适当时生成它们,而不是预先加载所有结果。

var matches =
    from fileName in filesToLookIn
    from line in File.ReadAllLines(fileName)
    from searchString in searchStrings
    where line.Contains(searchString)
    select new
    {
        FileName = fileName,
        SearchString = searchString
    };

    foreach(var match in matches)
    {
        references.AddIfNew(match.SearchString, match.FileName);
    }
编辑:

从概念上讲,查询将每个文件名转换为一组行,然后将该组行交叉连接到一组搜索字符串,这意味着每一行都与每个搜索字符串配对。该集合将过滤为匹配行,并选择每行的相关信息

multiple-from子句类似于嵌套的foreach语句。每一个都表示前一个迭代范围内的新迭代。多个from子句转换为该方法,该方法从每个元素中选择一个序列,并将结果序列展平为一个序列

C的所有查询语法都转换为扩展方法。然而,编译器确实使用了一些技巧。一种是使用匿名类型。当2+范围变量在同一范围内时,它们可能是幕后匿名类型的一部分。这允许任意数量的作用域数据流经扩展方法,如Select和Where,它们具有固定数量的参数。有关更多详细信息,请参阅

以下是上述查询的扩展方法翻译:

var matches = filesToLookIn
    .SelectMany(
        fileName => File.ReadAllLines(fileName),
        (fileName, line) => new { fileName, line })
    .SelectMany(
        anon1 => searchStrings,
        (anon1, searchString) => new { anon1, searchString })
    .Where(anon2 => anon2.anon1.line.Contains(anon2.searchString))
    .Select(anon2 => new
    {
        FileName = anon2.anon1.fileName,
        SearchString = anon2.searchString
    });

我不知道你可以像那样使用多个from语句。这到底是怎么回事?我对LINQ的体验完全是通过lambdas和扩展方法。这会转换为链式扩展方法吗?是的,多个from子句转换为对SelectMany扩展方法的调用。在Reflector中查看它,看看到底发生了什么。这就是我想要的,并且更好地表达了这个想法。谢谢你给SelectMany的提示,我不知道那里有。但是,我遇到了一个明显的性能差异:嵌套的“ForEach”需要229秒才能执行;LINQ嵌套“from”需要504秒。这是由于幕后的匿名对象创建/破坏造成的吗?我应该创建一个新的SO问题来解决这个问题吗?可能是。提出一个问题不会有什么坏处。
var matches = filesToLookIn
    .SelectMany(
        fileName => File.ReadAllLines(fileName),
        (fileName, line) => new { fileName, line })
    .SelectMany(
        anon1 => searchStrings,
        (anon1, searchString) => new { anon1, searchString })
    .Where(anon2 => anon2.anon1.line.Contains(anon2.searchString))
    .Select(anon2 => new
    {
        FileName = anon2.anon1.fileName,
        SearchString = anon2.searchString
    });