简体   繁体   中英

Handling null objects when calling methods

I have been looking around finding the best possible option for handling null objects in calling a method (or method chain).

It is our common practice to check with if condition:

if ( customObject != null ) {
    customObject.callMe();
}

Which can be further improved by using extension methods:

Program customObject = null;
if (customObject.NotNull()) {
    customObject.CallMe();
}

public static bool NotNull(this object o) {
    return o == null;
}

PLEASE NOTE: I normally ignore ! from my programming practice. Hence it is wise to say that for me extension methods are good to go.

However, it becomes very complicated in dealing with when the Method chain is involved.

customObject.CallMe().CallMe2() ex... 

How you do think it can be handled in C#, so that the CallMe is called only if customObject is not null and CallMe2 is called only if CallMe returns non null object.

Of course I can use If conditions or ternary operator. However, I would want to know if vNext, C#5.0 has some thing to offer.

In the upcoming C# 6 (vNext) has the ?. operator (Null Conditional Operator) which easily allow you to chain null reference checks for every nested property.

An example of this would be:

int? first = customers?.[0].Orders?.Count();

This was a requested feature in the Visual Studio UserVoice site

Add ?. Operator to C#

You can see the status for all the new language features for C# 6.0 on the Codeplex site for Roslyn:

C# 6 Language Features Status

You can currently write these kinds of extension methods:

public static class Extensions
{
    public static R IfNotNull<T, R>(this T @this, Func<T, R> @select) where T : class
    {
        return @this.IfNotNull(@select, () => default(R));
    }

    public static R IfNotNull<T, R>(this T @this, Func<T, R> @select, Func<R> @default) where T : class
    {
        return @this != null ? @select(@this) : @default();
    }

    public static void IfNotNull<T>(this T @this, Action<T> @action) where T : class
    {
        if (@this != null)
        {
            @action(@this);
        }
    }
}

Then call them like so:

customObject 
    .IfNotNull(y => y.CallMe())
    .IfNotNull(y => y.CallMe2());

C# 5 has nothing to offer to simplify your code. You are stuck with either:

  • Nested if conditions
  • Complicated ternary expressions
  • Englobing try/catch catching NullReferenceException (you really shouldn't do that if a null in your chain is not really exceptionnal, ie it should not happen, but you want a keepsafe).

However, the future may be brighter, as the null-propagating operator ?. is a proposed feature of C# 6, and has already be implemented is Roslyn , so it would be a great surprise if it was not effectively in C# 6. With this operator, you will be able to write

customObject?.CallMe()?.CallMe2()?.[...] 

And the compiler will create the nested if for you (and even ensure that every method is only called once). So, A little more patience...

C# 6 does have the ? operator, otherwise you can look into monads, particularly the Maybe monad , eg here , here , elsewhere . Is it worth using monads in a non-functional language that wasn't designed with them in mind is a different question.

Since people are tearing apart the answer that assumes you aren't an idiot, here is one that assumes you are.

You should have checked when customObject was created, because that is the point at which you would have know why you got a null . If you didn't check then, your best bet now is to let your code throw the exception. If there is still something you can do that makes perfect sense without having the object you clearly need, you can fix that up in the catch of the exception, but there probably isn't.

All of the mechanisms that ignore the significance of an unexpected null are just passing along the failure to have paid attention earlier, including habitual overuse of the proposed null-propagation operator, and all habits of checking objects before use. They are seeking an efficient response to a catastrophe that minimizes attention paid to it.

If you are signalling important information with the nullness of an object, that is probably a bad design choice, and you should have converted the null into more usable information when it was introduced.

Testing for the unexpected nullness of an object right before it is used, rather than right after it is produced cannot really help anyone. It is a code-smell that indicates you have misplaced responsibility, probably in one of these ways:

  1. You are unwilling to challenge people who make awkward design decisions without adequate explanation
  2. You do not feel you have control over something you have incorporated but are too lazy to wrap it
  3. You don't believe nulls should exist, and you are trying to ignore them, so you haven't thought through any policy on them.

The policy you indicate, of eating the null and continuing execution without analyzing it, is a bad one. If you intend to keep it, that is my other answer. You should instead do something reasonable.

In a lot of workplaces, putting an empty catch block anywhere is a clear documentation of future work. So it is doing something. But leaving it there is never an option that should stand in good code. Such a block would more reasonably convert the exception into a response that creates future work to fix the source of the error, even if the code-block implements a workaround that addresses the problem in a more local way.

This is the point of exception handling. If you want to respond to unusual circumstances like getting an unexpected null returned from a function, then catch the NullReferenceException , and call your recovery code from there. The exception itself will stop further calls, so if that is all you wish to accomplish, implement no recovery code. I do not suggest this is a reasonable response, something like this should generally at least be logged.

try
{
    customObject.CallMe().CallMe2()
}
catch (NullReferenceException ex)
{
   /* save enough details to determine why you got an unexpected null */
}

Do not use exception handling to accomplish normal programming tasks, but do not avoid it excessively, by checking for every possible contingency, no matter how rare. Decide what failures are likely in your code (that you can do anything about) and include a healthy list of catch 'es at the end of each reasonable-sized block.

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