【问题标题】:Debugging LINQ on a per-element basis基于每个元素调试 LINQ
【发布时间】:2013-05-01 13:03:54
【问题描述】:

我喜欢 LINQ 语句的表达性语法和其他方便的功能。但是,我发现有时调试它们很麻烦。具体来说,当我对集合运行 LINQ 语句并且集合中的某个元素导致异常时,我如何才能确定问题输入是什么以及问题来自何处?

假设我有一个包含 1000 个实数的文本文件:

0.46578
12.314213
1.444876
...

我将其作为List<string> 阅读并将其加载到更具体的数据结构中:

var file_contents = File.ReadAllLines("myfile.txt");
var data = file_contents.Select(s => double.Parse(s));

现在,对于这个特定的输入,我没有仔细查看它,结果发现第 876 行包含(显示的行号):

875    5.56786450
876    Error: Could not calculate value.
878    0.0316213

无论出于何种原因(也许该文件是由出现故障的脚本生成的)。我的 LINQ 方法链当然会抛出异常。问题是,我如何确定列表的哪个元素导致了异常,它的值是什么?

澄清一下,如果我使用了 for 循环:

var data = new List<double>();
foreach(var row in file_contents)
{
    var d = double.Parse(row);
    data.Add(d);
}

然后异常会突出显示调用double.Parse 的字符串,我可以将鼠标悬停在row 上以轻松查看问题输入是什么。

当然,我可以使用 Resharper 将我的 LINQ 语句转换为 for 循环,然后对其进行调试,但有没有更好的方法?

【问题讨论】:

  • 这是什么类型的 LINQ? EF 还是 LINQ to SQL?

标签: c# linq debugging visual-studio-2012


【解决方案1】:

在 lambda 函数上放置条件断点,条件为 s.StartsWith("5.56")。您只需将光标放在 lambda 上并按 F9。假设您使用的是 Visual Studio。

【讨论】:

    【解决方案2】:
    var data = file_contents.Select(s => {
        try
        {
           return double.Parse(s);
    
        }
        catch
        {
           throw; //breakpoint?
        }
    });
    

    【讨论】:

      【解决方案3】:

      免责声明:我为 OzCode 工作

      使用 Visual Studio 进行 LINQ 调试是不可能的。我建议您尝试使用 OzCode。

      这是您的代码在调试时的样子(第 6 项中的异常)。

      您可以通过调查传递给 Select 子句的项目来判断哪个项目导致了异常 - 由于最后一个项目触发了异常 - 很容易找到有问题的值。

      如果您有兴趣,可以尝试 OzCode 的 LINQ 调试 - 我们刚刚启动了EAP

      【讨论】:

        【解决方案4】:

        我会亲自使用 tryparse。

                var data = new List<string>
                    {
                        "0.46578",
                        "12.314213",
                        "Error: Could not calculate value.",
                        "1.444876",
                    };
                double d;
                var good = data.Where(s => Double.TryParse(s, out d)).Select(Double.Parse);
                var bad = data.Where(s => !Double.TryParse(s, out d)).Select(x => new
                    {
                        key = data.IndexOf(x),
                        value = x
                    }).ToDictionary(x => x.key, x => x.value);
        
        
                textBox1.AppendTextAddNewLine("Good Data:");
                WriteDataToTextBox(good);
        
                textBox1.AppendTextAddNewLine(String.Format("{0}{0}Bad Data:", Environment.NewLine));
                WriteDataToTextBox(bad);
        

        AppendTextAddNewLine 只是我为我的小概念验证测试程序编写的一个扩展方法

            public static void AppendTextAddNewLine(this TextBox textBox, string textToAppend)
            {
                textBox.AppendText(textToAppend + Environment.NewLine);
            }
        

        编辑

        WriteDataToTextbox 是一种将IEnumerble&lt;T&gt; 写入文本框的通用方法。

            void WriteDataToTextBox<T>(IEnumerable<T> data )
            {
                foreach (var row in data)
                {
                    textBox1.AppendTextAddNewLine(row.ToString());
                }
            }
        

        忘了把输出放在这里所以我想我应该这样做。它显示了坏数据的索引和导致问题的数据本身。

        Good Data:
        0.46578
        12.314213
        1.444876
        
        
        Bad Data:
        [2, Error: Could not calculate value.]
        

        【讨论】:

        • 非常感谢您的评论具有建设性。既然您有更好的方法,请您发布自己的答案,该答案不是“您见过的最糟糕的 LINQ 用法”。
        • 这里的主要问题是一个事实,您的解决方案迭代了 data 2+I 次,其中 I 是不正确的行数 - IndexOf 每次调用时都会从头开始迭代集合.您还 Parse 每行至少两次,其中大多数是三次(两个TryParse 和一个Parse 以获得良好的价值)。这不是很有效,是吗?我已经发布了我的答案,欢迎批评!
        • 让我向您展示当代码可以更有效地直接从您的 cmets 中获取而没有非建设性部分时,我将如何写评论。 “您的解决方案对数据进行了 2+I 次迭代,其中 I 是不正确的行数 - IndexOf 每次调用时都会从头开始迭代集合。您还至少解析每一行两次,其中大部分解析三遍(两个 TryParse 和一个解析好值)。已经有提供索引的 Select 重载.. 使用 .Select((s, i) => i 作为 indexOf"
        【解决方案5】:

        我不确定你为什么不喜欢 foreach 循环这里。 LINQ 无论如何都会在内部使用它,而且您已经意识到使用 LINQ 有一些优点和缺点,而调试是缺点之一。

        我可能会将 LINQ 与 foreach 混合使用,最终得到以下结果:

        // read all lines from file //
        var file_contents = File.ReadAllLines("myfile.txt");
        
        // set initial data list length to number of lines for better performance
        var data = new List<double>(file_contents.Length);
        
        // list for incorrect line numbers
        var incorrectRows = new List<int>();
        
        foreach (var x in file_contents.Select((s, i) => new {s, i}))
        {
            // x.s - line string
            // x.i - line number
        
            double value;
            if (double.TryParse(x.s, out value))
                data.Add(value);        // add value, which was OK
            else
                incorrectRows.Add(x.i); // add index of incorrect value
        }
        

        这将完全防止异常,并为您提供所有不正确值的行号。它还仅对file_contents 进行一次迭代,并且每个值仅被解析一次。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-03
          • 2023-04-05
          • 1970-01-01
          相关资源
          最近更新 更多