-1

Frequently in applications we encounter situations that could throw a NullReferenceException; for example, assuming the following method's argument is a user defined reference type, accessing the reference without first checking if it is null, can result in a NullReferenceException:

public void ProcessObject(MyObject obj) { ... }

The common way to ensure that these exceptions don't occur is to check the reference prior to accessing it:

public void ProcessObject(MyObject obj) {
    if (obj != null) {
        ...
    }
}

This is a relatively straight forward process, and allows us to perform some kind of logging (if we desire) if the reference is null. However, let's say that MyObject contains sub objects that we must utilize:

public sealed class MyA { public int X { get; set; } }
public sealed class MyB { ... }
public sealed class MyC { ... }
public sealed class MyD { ... }
public sealed class MyObject {
    public MyA A { get; set; }
    public MyB B { get; set; }
    ...
}

Ordinarily, I'd side with the idea that the handling in this situation is the exact same:

if (obj?.A != null)
    ...

However, let's assume that MyObject is a rather large data model, defined by an external source. Let's also assume that I need to access many of these sub-objects in a way that makes it incredibly tedious to check each individually:

if (obj?.A != null) {
    values.Add(obj.A.X)
    values.Add(obj.A.Y)
}
if (obj?.B != null)
    values.Add(obj.B.Z)
...

Instead, perhaps it's more convenient to create a last accessed object, assuming that if something fails there's no need to continue:

dynamic lastAccessedObject { Name = nameof(obj), Value = JsonConvert.SerializeObject(obj) };
try {
    values.Add(obj.A.X);
    values.Add(obj.A.Y);
    values.Add(obj.B.Z);
} catch (Exception e) when (e is NullReferenceException) {
    LogMessage($"An attempt was made to process null data; the last accessed object is: {JsonConvert.SerializeObject(lastAccessedObject)}");
}

This is a very simple example, but I believe it would be useful in a scenario where there are multiple top level objects and many sub-objects are utilized during the process. If the top level object isn't null, and we encounter an exception with one of its sub-objects, then the JSON data would reflect the sub-object as null. As such, from a readability point of view, I believe this method to be more concise, and as such, more legible than a method with many nested logical evaluations checking for null and logging what was null.


With that in mind, I'd like a second opinion on this idea. To prevent an X/Y problem, the goal is to simply record what object causes a null reference exception to be thrown, since the call stack doesn't always give us this information.

Will recording the last accessed object potentially confuse the purpose of the method? Are there any gotchas from a performance or runtime perspective that should be considered? What about from a peer review perspective? Does this method cause you to stop and think too much compared to the typical logical evaluations? Does it make sense to do this only if the situation dictates it, or not at all? These are just a few of my questions; feel free to answer them, but my primary question is simply:

Is recording the top level object as JSON data less legible than the typical process of logical evaluations?

7
  • So you're having trouble because you're getting null-reference exceptions? And you'd consider these null-references to be defects in an object-tree, so you'd like to know which parent-object had a null-reference such that you can track down the location of the null-reference in the object-tree?
    – Nat
    Sep 2, 2021 at 0:54
  • @Nat effectively, yes. Sep 2, 2021 at 1:03
  • From a prior question of yours, I get the impression that you're working on a heavy-weight modeling framework that'll hoist a simpler set of C# logic into a bunch of wrappers such that it can be managed. If you're doing that already, then it'd seem like a potentially good opportunity to add data-validation in. Generally it'd probably be easier to have a policy of validating data as it's set, rather than trying to retroactively trace down where a defect was only after a problem's occurred. You might design custom accessors that make it convenient to do stuff like null-checking.
    – Nat
    Sep 2, 2021 at 1:24
  • Non-nullable reference-types can also be really helpful. Perhaps so much so that they'd seem like the answer if you just wanted to watch out for null's. That said, this may be a larger problem with other data-validation, so a more general tactic might be appropriate.
    – Nat
    Sep 2, 2021 at 1:26
  • 1
    Perhaps this might be useful as a design? Make everything implement interface IValidatableObject (downside: anytime a design requires every type to implement an interface, the end result could be massive pollution. But for data objects, the benefits may justify this cost.)
    – rwong
    Sep 2, 2021 at 1:32

2 Answers 2

2

This is probably a bigger issue where we could have a lot of discussion on how to avoid/mitigate it and related issues.

That said, as a quick stop-gap, you might define an extension-method:

public static T QualifyNotNull<T>(
      this T objectThatShouldNotBeNull
  )
{
  if (System.Object.ReferenceEquals(
          objectThatShouldNotBeNull
        , null
      )
    )
  {
    throw new NullReferenceException();
  }

  return objectThatShouldNotBeNull;
}

Then whenever you access something that shouldn't be null, you can just qualify it with .QualifyNotNull().

For example, instead of

var x = obj.SomeProperty;

, it'd be

var x = obj.SomeProperty.QualifyNotNull();

.

9
  • 2
    I’m not sure that I see the benefit of an extension method over the use of a standard logical evaluation? Also, if obj.SomeProperty is null, can the extension itself be invoked without throwing a null ref? Additionally you’re correct that there are ways to mitigate the issue, and I’m taking those steps as well, but I’m trying to add as much detail as possible with error logging. I don’t actually get null ref often, but it’s always annoying to trace if I missed some validation and the hosting environments vary which can make things worse sometimes. Sep 2, 2021 at 1:39
  • Sorry if that came off odd or non-neutral in any way. Just trying to respond to everything before I pass out for the night. Sep 2, 2021 at 1:39
  • 2
    @Tacoタコス: "Also, if obj.SomeProperty is null, can the extension itself be invoked without throwing a null ref?": Yup! Actually that's a really cool thing about writing a method as an extension-method rather than an instance-method: you can call it on a null-object without error.
    – Nat
    Sep 2, 2021 at 1:41
  • @Tacoタコス: I do suspect you've got a bit of an X/Y problem, but if you want to know how to do exactly what you were asking about.. basically you need to use reflection to make a special mode of accessing objects' properties/fields. Then you can keep a log of all object-accesses. If you're already wrapping everything (as I suspect in the special case of the project you seem to be working on), then you could add this functionality in your wrappers.
    – Nat
    Sep 2, 2021 at 1:48
  • @Tacoタコス: For example, you could make an extension-method like .GetProperty(this T parentObject, string propertyName) that basically calls parentObject.propertyName, which you could make work by having the extension-method reflect the named-property, cache its result, log the parent-object, then return the cached-result.
    – Nat
    Sep 2, 2021 at 1:53
1

A general solution is not possible. You can't always display the source of the null reference because it doesn't always have a name. Consider this example:

var name = (someObject ?? someOtherObject).Name;

Which object should be shown in the exception message? Neither, really, because the offender is a local anonymous temporary variable that contains the result of the null coalesce operation. So that's a no-go.

But, if we restrict the problem to the simple mapping of properties from one object to another, and we don't care about which specific object instance, we can write a helper function that provides a better exception.

T Check<T>(Expression<Func<T>> expression)
{
    object o = Expression.Lambda(expression).Compile().DynamicInvoke();

    //Value is not null so just return it
    if (o is T) return (T)o;

    var memberExp = expression.Body as MemberExpression;
    if (memberExp is null)
    {
        //We don't know how to get a class/property name, so throw a null reference exception with a vague message
        throw new NullReferenceException("A non-member expression was null.");
    }
    var message = string.Format
    (
        "Null value detected at {0}.{1}",
        memberExp.Member.DeclaringType.Name,
        memberExp.Member.Name
    );

     //Throw with a much better exception message
    throw new NullReferenceException(message);
}

Now you could do something like this:

var person = new Person { Name = "Bob Jones", Address = null };
var city = Check( () => person.Address.City );

This will throw an exception with this message:

Null value detected at Address.City

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.