您可以捕获引发NullReferenceException的对象的名称吗?

时间:2019-03-29 14:39:56

标签: c# logging nullreferenceexception

是否有办法找出导致特定原因的{em {em} ?我已经阅读了有关troubleshooting NullReferenceExceptions的页面,其中讨论了在调试器中检查变量并查看异常消息。

如果在生产代码中引发了异常,那么您将无法运行调试器来检查变量,该怎么办?异常消息显示了堆栈跟踪,因此您可以查看引发异常的方法,但没有说明哪个特定对象是NullReferenceException

我希望能够将null对象的名称添加到错误消息中,以便在查看用户的报告时遇到null时,我可以轻松查看NullReferenceException是什么对象并进行修复。有人知道这样做的方法吗?

我还发现this question提出了同样的问题,但这是从2011年开始的,我不知道此后是否有任何变化。

编辑The question标记为重复的确实是重复的,但也很旧(2008年)。从那以后有什么改变吗?

编辑2 :我在搜索此问题时发现了this。 Visual Studio可以告诉您引发null的原因;有什么办法可以将其添加到日志文件中?

3 个答案:

答案 0 :(得分:3)

在给定stacktrace的情况下应该相对容易找出,但是更好的方法是在代码中包括“验证”或参数和/或空检查,并在尝试进行操作之前明确地抛出ArgumentNullException访问可能尚未初始化的变量的成员。然后,您可以提供未初始化对象的名称:

if (obj == null)
    throw new ArgumentNullException(nameof(obj));

在构造函数和方法中对参数执行这些检查是一种常见的做法,例如:

public void SomeMethod(SomeType someArgument)
{
    if (someArgument == null)
        throw new ArgumentNullException(nameof(someArgument));

    //you will never get there if someArgument is null...
    var someThing = someArgument.SomeMember;

    if (someThing == null)
       throw new ArgumentException("SomeMember cannot be null.", nameof(someArgument));
    ...
}

答案 1 :(得分:2)

TL; DR 您的问题的答案是否,不可能。 本文谈论源代码位置而不是对象。但是,您共享的article涵盖了很多答案,如果您完整阅读了该书,您将知道为什么不可能。为了大家的利益,我将在此处添加摘录。

  • 基于目前为止可用的程序集元数据,运行时可以推断出问题的位置,而不是对象的位置。
  • 不能保证PDB始终在其中,并具有将IL织回到n标识符所需的信息。
  • 不仅是C#,而且还需要其他许多语言支持,才能在.NET运行时中实现此功能,这在目前不太可能。

程序集元数据没有调试信息

在运行时期间找到对象的名称要求调试信息可用,该信息基于您用来构建代码的配置。不能保证运行时可以编织地址或注册一个名称。程序集元数据包含对程序集的描述,数据类型和成员及其声明和实现,对其他类型和成员的引用,安全权限,但不包含源信息。

由于您无法控制框架和库(nuget)代码,因此使用PDB会使其不一致

我认为甚至不可能始终如一地做到这一点,即使所有面向CLR的编译器都发出了有关标识符的足够信息(所有语言编译器),并且运行时使用了它。鉴于我可以想到来自社区/ NuGet的参考二进制文件的任何.NET项目,.NET项目的编译方式将不一致。在这种情况下,一部分代码报告标识符名称,而另一部分则不会。

考虑生成的类型(例如IEnumerable)会发生什么情况,运行时可以找出并报告IEnumerable.Current为null,但容器中的基础对象仍然为null,但仍然无法给出答案。您遍历堆栈并找出基础对象并对其进行修复,即使没有信息也是如此。

考虑多线程代码,您可能在其中知道哪个对象为空,但是您可能不知道哪个上下文/调用堆栈导致该对象为空。

所以我的结论是,

我们应该尝试从方法而不是标识符中推断上下文。标识符告诉您什么是空值,但是通常您需要弄清楚为什么它为空值,因为程序员没有预料到它,所以他必须走回栈以找出问题所在。如果对象是局部变量,则这是程序员的错误,可能不必是运行时才能解决。

答案 2 :(得分:1)

每当引发异常时,都会引发AppDomain.CurrentDomain.FirstChanceException。您可以向此事件添加处理程序,以监视引发了多少异常以及运行时从何处抛出异常。在事件处理程序中,您可以访问实际的Exception对象。如果需要某种特殊类型的异常,则只需检查传递给处理程序的事件参数对象上的Exception属性的类型。

以下示例将所有异常(包括内部异常)输出到文本文件(包括堆栈跟踪),以供以后分析。由于通常会捕获并重新抛出异常,因此相同的异常可能在输出文件中多次出现,并且堆栈跟踪越来越长。使用这样的文件,您可以查找特定类型的异常的来源。您还可以从此类文件中获取频率和出现次数以及其他类型的信息。

AppDomain.CurrentDomain.FirstChanceException += (sender, e) =>
{
    if (exceptionFile is null)
        return;

    lock (exceptionFile)
    {
        if (!exportExceptions || e.Exception.StackTrace.Contains("FirstChanceExceptionEventArgs"))
            return;

        exceptionFile.WriteLine(new string('-', 80));
        exceptionFile.Write("Type: ");

        if (e.Exception != null)
            exceptionFile.WriteLine(e.Exception.GetType().FullName);
        else
            exceptionFile.WriteLine("null");

        exceptionFile.Write("Time: ");
        exceptionFile.WriteLine(DateTime.Now.ToString());

        if (e.Exception != null)
        {
            LinkedList<Exception> Exceptions = new LinkedList<Exception>();
            Exceptions.AddLast(e.Exception);

            while (Exceptions.First != null)
            {
                Exception ex = Exceptions.First.Value;
                Exceptions.RemoveFirst();

                exceptionFile.WriteLine();

                exceptionFile.WriteLine(ex.Message);
                exceptionFile.WriteLine();
                exceptionFile.WriteLine(ex.StackTrace);
                exceptionFile.WriteLine();

                if (ex is AggregateException ex2)
                {
                    foreach (Exception ex3 in ex2.InnerExceptions)
                        Exceptions.AddLast(ex3);
                }
                else if (ex.InnerException != null)
                    Exceptions.AddLast(ex.InnerException);
            }
        }

        exceptionFile.Flush();
    }
};

(示例来自GitHub上的IoT Gateway项目,已获得许可)。