简体   繁体   中英

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

Is there a way to find out what specific object caused a NullReferenceException ? I've read the page about troubleshooting NullReferenceException s and it talks about inspecting variables in the debugger and looking at the exception message.

What if the exception was thrown in production code so you can't run a debugger to inspect the variables? The exception message shows the stack trace so you can see what method the exception was thrown in, but it does not say which specific object was null .

I'd like to be able to add the name of the object that was null to the error message so that when I'm looking into reports from users and I come across a NullReferenceException , I can easily see what object was null and fix it. Does anyone know of a way to do this?

I also found this question which asked the same thing, but it was from 2011 and I don't know if anything has changed since then.

Edit : The question that this is flagged as a duplicate is indeed a duplicate but is also very old (2008). Has anything changed since then?

Edit 2 : I found this when googling this question. Visual Studio can tell you what threw the NullReferenceException ; is there any way to tap into this to add it to a log file?

It should be relatively easy to figure out given the stacktrace, but a better approach would be to include "validation" or parameters and/or null checks in your code and explicitly throw a ArgumentNullException yourself before you try to access a member of a variable that may not has been initialized. You can then supply the name of the uninitialized object:

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

It's a common practise to perform these checks on arguments in both constructors and methods, eg:

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 The answer to your question is No, it's not possible . The article speaks about the source code location and not the object. But much of the answer is covered in the article you shared, and if you read it fully you will know why it's not possible. For the benefit of everyone i will add the excerpt here.

  • Based on the available assembly metadata as of today, the runtime can infer the location of the problem and not the object.
  • There are no guarantees that the PDB is always there with required information to weave the IL back to an identifier.
  • It's not just C#,but a bunch of other languages have to support this to make this work in the .NET runtime, which is less likely at this moment.

Assembly Metadata doesn't have debug information

Finding a name of an object during runtime requires debug information to be available, which is based on the configuration you used to build your code. There is no guarantee that the runtime can weave an address or register to a name. The assembly metadata contains the description of the Assembly , Data Types and members with their declarations and implementations, references to other types and members , Security permissions but doesn't contain source information.

Using PDB's would make it inconsistent as you don't have control over framework and library (nuget) code

I think it might not even be possible to do this consistently, Even if all compilers targeting CLR, emit enough information about identifiers (all language compilers) and the runtime consume it. The way the .NET project gets compiled won't be consistent given the fact that any .NET project that i can think of reference binaries from community / NuGet. In that case a part of the code report identifier names and the other part wouldn't.

Consider what happens to the generated types (for example IEnumerable) The runtime can figure out and report that IEnumerable.Current is null, but what is null is the underlying object in the container, which still doesn't give the answer. You walk the stack and figure out the underlying object and fix it, which is the case even without the information.

Consider multithreaded code, where you might know which object is null, but you might not know which context / call stack caused it to be null.

So my conclusion is,

we should try to infer context from methods than identifiers. Identifiers tell you what is null, but often you need to figure out why it is null because the programmer didn't anticipate it, he has to walk the stack back to figure out the issue. In case where the object is a local variable it's a programmer's error and might not have to be the runtime to figure it out.

Whenever an Exception is thrown, AppDomain.CurrentDomain.FirstChanceException is raised. You can add a handler to this event, to monitor how many exceptions are thrown, and from where during runtime. In the event handler, you have access to the actual Exception object. If a particular type of exception is of interest, you simply check the type of the Exception property on the event arguments object passed to the handler.

The following example outputs all exceptions (including inner exceptions) to a text file, including stack traces, for analysis later. Since exceptions are often caught and re-thrown, the same exception might occur multiple times in the output file, with longer and longer stack traces. Using such a file allows you to find the source of particular types of exceptions. You can also get frequency and occurrence, and other types of information from such a file.

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();
    }
};

(Example from the IoT Gateway project on GitHub, with permission).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM