如何对以下循环进行 LINQ 化?

How to LINQ-ify the following loop?

提问人:Dr. Brian Hart 提问时间:7/21/2022 最后编辑:Dr. Brian Hart 更新时间:8/6/2022 访问量:65

问:

我在 C# 程序中有一个方法。它枚举特定文件夹中的所有文件,然后遍历列表。对于每个文件,我都阅读了所有使用在上面的行。我只想处理一个文件,如果它包含一个类,无论是传统的,还是其名称以某个短语开头并且不以单词结尾的,我希望在包含类---声明的行中找到行索引,即部分。.csFile.ReadAllLinesstatic,abstract,Tests.public static class Foo

假设我接受 的结果并调用它来创建一个 ,我希望使用该方法使用谓词找到与我的条件(如果存在)匹配的行的索引。File.ReadAllLinesToList()List<string>FindIndex

我的问题是:写这样一个谓语的好方法是什么?

我意识到我可能会使用更复杂的方法,但我只是将此代码放入一个快速而肮脏的 LINQPad 脚本中。所以,我不必变得超级花哨。

让我向你展示我到目前为止所拥有的(假设最外层的命名空间和类已经适当地声明了):

void Main()
{
    var files = Directory
        .EnumerateDirectories(
            Path.Combine(
                Environment.GetFolderPath(
                    Environment.SpecialFolder.UserProfile
                ), @"source\repos\astrohart\MFR"
            ), "*", SearchOption.TopDirectoryOnly
        ).SelectMany(
            x => Directory.EnumerateFiles(
                x, "FileSystemEntry*.cs", SearchOption.AllDirectories
            )
        )
        .Where(x => !"FileSystemEntry.cs".Equals(Path.GetFileName(x)))
        .ToList();
    if (files == null || !files.Any()) return;

    foreach (var file in files)
    {
        var contents = string.Empty;

        try
        {
            contents = File.ReadAllText(file);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"ERROR: {ex.Message}");

            contents = string.Empty;
        }

        if (string.IsNullOrWhiteSpace(contents)) continue;
        if (contents.Contains("[TestFixture]")) continue;
        if (contents.Contains("[Log(AttributeExclude = true)]")) continue;

        file.Dump();

        var lines = new List<string>();
        lines.TrimExcess();

        try
        {
            lines = File.ReadAllLines(file).ToList();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"ERROR: {ex.Message}");

            lines = new List<string>();
            lines.TrimExcess();
        }

        if (lines == null || !lines.Any()) continue;

        var index = -1;

        for (var i = 0; i < lines.Count; i++)
        {
            var currentLine = lines[i].Trim();
            if (currentLine.EndsWith("Tests")) continue;

            if (currentLine.StartsWith("public static class FileSystemEntry"))
            {
                index = i;
                break;
            }
            if (currentLine.StartsWith("public class FileSystemEntry"))
            {
                index = i;
                break;
            }
            if (currentLine.StartsWith("public abstract class FileSystemEntry"))
            {
                index = i;
                break;
            }
        }

        if (index < 0) continue;
     
        /*...*/
    }
}

如何翻译循环:for

var index = -1;

for (var i = 0; i < lines.Count; i++)
{
    var currentLine = lines[i].Trim();
    if (currentLine.EndsWith("Tests")) continue;

    if (currentLine.StartsWith("public static class FileSystemEntry"))
    {
        index = i;
        break;
    }
    if (currentLine.StartsWith("public class FileSystemEntry"))
    {
        index = i;
        break;
    }
    if (currentLine.StartsWith("public abstract class FileSystemEntry"))
    {
        index = i;
        break;
    }
}

if (index < 0) continue;

因此,进入呼叫:

var index = lines.FindIndex(currentLine => /*...*/);

我需要有关如何派生与循环功能相匹配的 lambda 表达式的正确主体的帮助。for

提前致谢!

编辑 1

我眯起眼睛看了看我的环。我正在寻找专门用于该方法的谓词。我想得更仔细一点,我发现也许我可以侥幸逃脱:FindIndex

var index = lines.FindIndex(currentLine => !currentLine.Trim.EndsWith("Tests") && currentLine.Trim().StartsWith("public static class FileSystemEntry") || currentLine.Trim().StartsWith("public class FileSystemEntry") || currentLine.Trim().StartsWith("public abstract class FileSystemEntry"));

也许我可以实现一个扩展方法

public static bool StartsWithAnyOf(this string value, params string[] testStrings)
{
    var result = false;

    try
    {
        if (string.IsNullOrWhiteSpace(value.Trim())) return result;
        if (testStrings == null || !testStrings.Any()) return result;

        foreach(var element in testStrings)
            if (value.Trim().StartsWith(element))
            {
                result = true;
                break;
            }
    }
    catch
    {
        result = false;
    }

    return result;
}

然后我会声明另一个方法:

public static bool KeepLine(string currentLine)
{
     if (string.IsNullOrWhiteSpace(currentLine.Trim())) return false;
     if (currentLine.Trim().EndsWith("Tests")) return false;
     
     return currentLine.StartsWithAnyOf(
         "public static class FileSystemEntry",
         "public class FileSystemEntry",
         "public abstract FileSystemEntry"
     );
}

然后这样使用它:

var index = lines.FindIndex(KeepLine);

那行得通吗?

C# 性能 LINQ 查询优化 布尔逻辑

评论


答:

0赞 Amogh Sarpotdar 7/21/2022 #1

我还没有对此进行彻底测试,但如果我与上面提供的原始代码进行比较,它似乎通过了基本的理智。请注意,在衡量性能时,这并不是最好的。具有匿名函数的“foreach”循环存在缺陷,您无法摆脱匿名函数。摆脱 foreach 的唯一方法是运行所有 foreach 语句。为了保留条件与行内容匹配的第一个索引,我在else if()比较语句中使用了索引。这意味着 foreach 循环将对所有行运行,即使首次找到匹配行。

lines.ForEach((l) =>
{
    if (l.EndsWith("Tests")) ;
    else if (index ==0 && (l.StartsWith("public static class FileSystemEntry") ||
    l.StartsWith("public class FileSystemEntry") ||
    l.StartsWith("public abstract class FileSystemEntry")))
    {
        index = lines.IndexOf(l);
    }
});

评论

0赞 Dr. Brian Hart 8/6/2022
我很困惑,你不能通过简单地在正确的行(而不是)放置一个语句来打破早期等等......在 lambda 中,该方法的工作方式是,它只是为集合的每个成员调用 lambda,因此 lambda 表达式中的语句只是充当传统循环中的语句。是的,这是有道理的。起初,你的解释让我有点反感,但一旦我想清楚,你说的就有道理了。:-)ForEachreturn;break;ForEachreturn;continue
0赞 Dr. Brian Hart 8/6/2022
感谢您深思熟虑的回答,但我想我真的需要一个用于我的 lambda 表达式的谓词方法,该方法可以传递给 的输入,而不是调用。FindIndexForEach