【问题标题】:Can you capture the name of the object that throws a NullReferenceException?你能捕捉到抛出 NullReferenceException 的对象的名称吗?
【发布时间】:2019-03-29 14:39:56
【问题描述】:

有没有办法找出导致NullReferenceException特定 对象?我已经阅读了有关troubleshooting NullReferenceExceptions 的页面,它谈到了在调试器中检查变量并查看异常消息。

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

我希望能够将 null 的对象名称添加到错误消息中,这样当我查看来自用户的报告并遇到 NullReferenceException 时,我可以很容易地看到null 是什么对象并修复它。有谁知道这样做的方法吗?

我还找到了this question,它问了同样的问题,但它是从 2011 年开始的,我不知道从那时起是否有任何变化。

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

编辑 2:我在谷歌搜索这个问题时发现了 this。 Visual Studio 可以告诉你是什么扔了NullReferenceException;有没有办法利用它来将它添加到日志文件中?

【问题讨论】:

  • “它没有说明哪个特定对象为空” - 记录的异常应包含带有方法名称和行号的堆栈跟踪。您可以简单地查看内部资源,看看可能是什么问题。
  • @Sinatr 如果同一行上有多个对象,这将无济于事。哪个对象为空?
  • 这正是堆栈跟踪所做的。当事情发生时,繁荣。错误冒泡,堆栈跟踪记录了所有内容。
  • @mxmissile 我同意它是重复的,但接受的答案是从 2008 年开始的。我想看看从那时起是否有任何变化。

标签: c# logging nullreferenceexception


【解决方案1】:

考虑到堆栈跟踪,应该相对容易弄清楚,但更好的方法是在代码中包含“验证”或参数和/或空检查,并在尝试访问之前自己显式抛出 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));
    ...
}

【讨论】:

  • 我在我的代码中执行此操作,但并非生产中的所有代码都是我的。这没有回答我的问题,即:如何获取引发 NullReferenceException 的变量的名称?
  • 除非您(或第三方软件)抛出指定正确名称的ArgumentNullException,否则您的问题的答案是否定的。
【解决方案2】:

TL;DR 您的问题的答案是不,不可能。 这篇文章谈论的是源代码位置而不是对象。但是大部分答案都包含在您分享的article 中,如果您完全阅读它,您就会知道为什么它不可能。为了大家的利益,我将在此处添加摘录。

  • 根据目前可用的程序集元数据,运行时可以推断问题的位置,而不是对象。
  • 不能保证 PDB 总是存在所需的信息以将 IL 编织回 n 标识符。
  • 不仅仅是 C#,许多其他语言都必须支持这一点,才能在 .NET 运行时中实现这一点,目前这种可能性不大。

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

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

使用 PDB 会导致不一致,因为您无法控制框架和库 (nuget) 代码

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

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

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

所以我的结论是,

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

【讨论】:

    【解决方案3】:

    每当抛出异常时,都会引发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 项目的示例,已获得许可)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-16
      • 1970-01-01
      • 2016-05-21
      • 2019-10-24
      • 1970-01-01
      • 2021-08-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多