6

Is there a way to find out what specific object caused a NullReferenceException? I've read the page about troubleshooting NullReferenceExceptions 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?

Lews Therin
  • 3,707
  • 2
  • 27
  • 53
  • Rand Al'thor, this is a possible duplicate of [Detecting what the target object is when NullReferenceException is thrown](https://stackoverflow.com/questions/115573/detecting-what-the-target-object-is-when-nullreferenceexception-is-thrown) – mxmissile Mar 29 '19 at 14:43
  • 2
    *"it does not say which specific object was null"* - logged exception should contain stack trace with method name and line number. You can simply look inside sources and see what could be the problem. – Sinatr Mar 29 '19 at 14:43
  • @Sinatr That doesn't help if there are multiple objects on the same line. Which object is null? – Lews Therin Mar 29 '19 at 14:45
  • That's exactly what stack trace do. When something goes Boom. The error bubble up and the stack trace record everything. – xdtTransform Mar 29 '19 at 14:46
  • @mxmissile I agree it is a duplicate but the accepted answer is from 2008. I was asking to see if anything has changed since then. – Lews Therin Mar 29 '19 at 14:47
  • Well the object doesn't exist so there is no information about it. – xdtTransform Mar 29 '19 at 14:47
  • @xdtTransform Huh? The variable name of the object exists, it's just set to `null`. That's the information I'm looking for. – Lews Therin Mar 29 '19 at 14:48
  • @LewsTherin, i am nitpicking here, but if the variable is `null` then there is no object. Yes, the variable has a name, but that does not mean that an object (with or without a name) exist, and a non-existing object cannot have a name. Note that the name of the variable is not synonymous with an object having a name... –  Mar 29 '19 at 14:50
  • you can use remote debugger and then can attach your code to the process. This way your production code will start hitting breakpoints. Otherwise you can write detailed logs. here is the link https://learn.microsoft.com/en-us/visualstudio/debugger/remote-debugging?view=vs-2017 – Manpreet Singh Dhillon Mar 29 '19 at 14:51
  • @elgonzo I agree but I'm not looking for the object. I'm fully aware it doesn't exist (which is the reason for the NRE). The information I want is the variable name that is supposed to be holding a reference to that non-existent object. – Lews Therin Mar 29 '19 at 14:51
  • @LewsTherin, yes, i am aware of that. mm8's answer below is what is usually done to detect and report (via exception) a variable reference being unexpectedly `null`. –  Mar 29 '19 at 14:55
  • @mm8 I do. This isn't my code. – Lews Therin Mar 29 '19 at 15:00
  • Not sure why you put a bounty on this. The feature is not there. Only Microsoft can change this, and it's not planned so far. Even if it's in Visual Studio (debugger), you will not be able to ship that to end user or servers. – Simon Mourier Apr 30 '20 at 08:27
  • Your reference to the new feature in 2017 concluded that they have added feature to highlight the place where the exception is thrown. But not the object that caused the exception. – Soundararajan May 04 '20 at 01:48

3 Answers3

6

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, e.g:

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));
    ...
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • I do this in my code, but not all of the code in production is mine. This doesn't answer my question, which was: how do I obtain the name of the variable that raised the NullReferenceException? – Lews Therin Mar 29 '19 at 14:59
  • 1
    Unless you (or the third-party software) throws an `ArgumentNullException` that specifies the correct name, the answer to your question is no. – mm8 Mar 29 '19 at 15:00
2

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 a n 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.

Soundararajan
  • 2,000
  • 21
  • 23
0

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).

Peter Waher
  • 178
  • 1
  • 8