簡體   English   中英

你能捕獲拋出 NullReferenceException 的對象的名稱嗎?

[英]Can you capture the name of the object that throws a NullReferenceException?

有沒有辦法找出導致NullReferenceException特定對象? 我已經閱讀了有關NullReferenceException故障排除的頁面,它談到了在調試器中檢查變量並查看異常消息。

如果異常是在生產代碼中引發的,因此您無法運行調試器來檢查變量怎么辦? 異常消息顯示堆棧跟蹤,因此您可以查看拋出異常的方法,但它沒有說明哪個特定對象是null

我希望能夠將null對象的名稱添加到錯誤消息中,這樣當我查看用戶報告並遇到NullReferenceException ,我可以輕松查看哪個對象為null並修復它. 有誰知道這樣做的方法嗎?

我也發現這個問題提出了同樣的問題,但它是從 2011 年開始的,我不知道從那時起是否有任何變化。

編輯這個問題,這是標記為重復的確是重復的,但也是很老的(2008)。 從那以后有什么改變嗎?

編輯 2 :我在谷歌搜索這個問題時發現了一點。 Visual Studio 可以告訴您是什么引發了NullReferenceException 有什么方法可以利用它將其添加到日志文件中嗎?

鑒於堆棧跟蹤應該相對容易找出,但更好的方法是在代碼中包含“驗證”或參數和/或空檢查,並在嘗試訪問變量成員之前自己顯式拋出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));
    ...
}

TL;DR 您的問題的答案是否定的,這是不可能的 這篇文章談論的是源代碼位置而不是對象。 但是大部分答案都包含在您分享的文章中,如果您完全閱讀它,您就會知道為什么這是不可能的。 為了大家的利益,我將在此處添加摘錄。

  • 根據目前可用的程序集元數據,運行時可以推斷出問題的位置,而不是對象。
  • 無法保證 PDB 始終存在所需的信息以將 IL 編織回標識符。
  • 不僅僅是 C#,還有很多其他語言必須支持這一點,才能在 .NET 運行時中實現這一點,目前不太可能。

程序集元數據沒有調試信息

在運行時查找對象的名稱需要可用的調試信息,這基於您用於構建代碼的配置。 不能保證運行時可以編織地址或注冊到名稱。 程序集元數據包含程序集的描述、數據類型和成員及其聲明和實現、對其他類型和成員的引用、安全權限,但不包含源信息。

使用 PDB 會使其不一致,因為您無法控制框架和庫 (nuget) 代碼

我認為甚至可能無法始終如一地執行此操作,即使所有針對 CLR 的編譯器都發出有關標識符(所有語言編譯器)的足夠信息並且運行時使用它。 考慮到我可以想到來自社區/NuGet 的引用二進制文件的任何 .NET 項目,編譯 .NET 項目的方式將不一致。 在這種情況下,代碼的一部分報告標識符名稱,而另一部分則不會。

考慮生成的類型(例如 IEnumerable)會發生什么,運行時可以找出並報告 IEnumerable.Current 為 null,但是 null 是容器中的底層對象,它仍然沒有給出答案。 您遍歷堆棧並找出底層對象並修復它,即使沒有信息也是如此。

考慮多線程代碼,您可能知道哪個對象為空,但您可能不知道哪個上下文/調用堆棧導致它為空。

所以我的結論是,

我們應該嘗試從方法而不是標識符中推斷上下文。 標識符告訴你什么是空值,但通常你需要弄清楚為什么它是空值,因為程序員沒有預料到它,他必須返回堆棧來找出問題。 如果對象是局部變量,則這是程序員的錯誤,可能不必是運行時才能弄清楚。

每當拋出異常時,都會引發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項目,經許可)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM