【问题标题】:How to LINQ-ify the following loop?如何对以下循环进行 LINQ 化?
【发布时间】:2022-08-06 11:12:57
【问题描述】:

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

鉴于我采用File.ReadAllLines 的结果并在其上调用ToList() 以创建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 => /*...*/);

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

提前致谢!

编辑 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# performance linq query-optimization boolean-logic


    【解决方案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);
        }
    });
    

    【讨论】:

    • 我很困惑,你不能通过简单地将return; 语句放在正确的行(而不是break;)来提前打破ForEach 等等...在一个lambda 中,ForEach 方法的工作方式,它只是为集合的每个成员调用 lambda,因此 lambda 表达式中的 return; 语句仅充当传统循环中的 continue 语句。是的,这是有道理的。起初,你的解释让我有点不适应,但一旦我仔细考虑,你说的很有道理。 :-)
    • 感谢您的周到回答,但我想我真的需要一个可以传递给FindIndex 输入的 lambda 表达式的谓词方法,而不是ForEach 调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-29
    • 2018-05-06
    • 1970-01-01
    相关资源
    最近更新 更多