开发者

Checking for null in an object hierarchy

开发者 https://www.devze.com 2022-12-10 17:14 出处:网络
I have a large C# (3.0) object structure originatin开发者_JS百科g from a deserialized XML document. I need to know whether a variable deep in the hierarchy is null. The way I do this now is to check e

I have a large C# (3.0) object structure originatin开发者_JS百科g from a deserialized XML document. I need to know whether a variable deep in the hierarchy is null. The way I do this now is to check every parent object on the way down for null, but this leads to a long repetition of if statements.

I am trying to avoid expensive try-catch blocks.

Is there a smarter way to do this?

edit: For example after a deserialization of an XML application form into an object hierarchy structure there could potentially be a salary value under

applicationForm.employeeInfo.workingConditions.salary

but to find out safely I have to write something like

if (applicationForm.employeeInfo != null)
  if (applicationForm.employeeInfo.workingConditions != null)
    if (applicationForm.employeeInfo.workingConditions.salary != null)

because simply using the latter if-statement will of course fail if one of the parent objects is null.

So I am looking for any smarter way to handle this situation.


You've run into the classic situation where each step in A.B.C.D may yield null. Though this is a common scenario, surprisingly there's no common pattern for solving it, other then using a large if-statement with lots of or's (||).

If each step can return a different class, then there's a rarely used pattern that you can apply: Use a generalized generic extension method with method chaining.

A generalized extension method is not a fixed term but I use it here for emphasizing that the ext. method is applicable to almost all types of objects, hence generalized. According to Bill Wagner in Effective C#, this is bad design. But in some remote cases, like yours, you can use it as long as you know what you're doing and why.

The trick is simple: define a generic extension method and generalize it for all classes that have a default constructor. If the test fails (object is null), the method returns a new object of the same type. Otherwise, it will return the unchanged object itself.

Why is this a good approach for your scenario? Because you don't need to change any existing classes, because it's understandable and promotes readable code, because it keeps type safety (compile time errors instead of runtime errors) and it's simply more concise compared to other approaches.

// extension method:
public static class SomeExtentionMethods
{
    public static T SelfOrDefault<T>(this T elem)
        where T : class, new()     /* must be class, must have ctor */
    {
        return elem ?? new T();    /* return self or new instance of T if null */
    }
}

// your code now becomes very easily readable:
Obj someObj = getYourObjectFromDeserializing();

// this is it applied to your code:
var mySalary = applicationForm.SelfOrDefault().
    employeeInfo.SelfOrDefault().
    workingConditions.SelfOrDefault().
    salary;

// now test with one if-statement:
if(mySalary.IsEmpty())
   // something in the chain was empty
else
   // all's well that ends well :)

The beauty of this approach is that it works with all types of classes (provided they have a ctor), including collections and arrays. If any step is an index step, it will still work (depending on the collection, an invalid index can return null, default or raise an exception):

var x = 
    someObj.SelfOrDefault()
    .Collection.SelfOrDefault()
    .Items[1].SelfOrDefault()
    .Mother.SelfOrDefault()
    .Father.SelfOrDefault();

Update: expanded a bit and added a more elaborate example
Update: renamed NotNull, which implies boolean, to SelfOrDefault, which follows LINQ's naming convention (FirstOrDefault etc) and implies what it does.
Update: rewritten and reorganized, made code more applicable, hoping to make it more understandable overall :)


Firstly, if you're repeating the logic in more than one place, encapsulate it in a method.

Secondly, you don't need lots of if statements, you just need one with a lot of OR conditions:

if(parent==null || 
   parent.Child == null || 
   parent.Child.GrandChild == null ...

Thirdly, "avoiding expensive try/catch blocks" may be a premature optimization, depending on your scenario. Have you actually tried this and profiled it, and is it really causing a large overhead?


You can nest ternary operators. Still a pain, but not as bad as nested ifs.

string salary = (applicationForm.employeeInfo == null) ? null :
                (applicationForm.employeeInfo.workingConditions == null) ? null :
                applicationForm.employeeInfo.workingConditions.salary;

If you just want to know whether or not it is null:

bool hasSalary = (applicationForm.employeeInfo == null) ? false :
                 (applicationForm.employeeInfo.workingConditions == null) ? false :
                 (applicationForm.employeeInfo.workingConditions.salary != null);


Here's my simple solution:

if (applicationForm?.employeeInfo?.workingConditions?.salary != null)

Here any of these objects (applicationForm, employeeInfo, workingConditions, salary) can be null and it will resolve without error.


can't you iterate?

for (SomeObject obj = someInstance; obj != null; obj = obj.Parent) {
    //do something
}


Use reflection.

Create a helper method that takes a parent object and a hierarchical dot notated string for your property names. Then use PropertyInfo and use recursion to go down one property at a time each time checking for null and returning null if so, else continuing down the hierarchy.


Since you didn't provide all too many details I had to fill in alot of the blanks. Here is a psuo-codeish example of how you might accomplish this via recursion

    public bool doesHierarchyContainANull(MyObject ParentObject)
    {

        if (ParentObject.getMemberToCheckForNull() == null)
            return true;
        else if (ParentObject.isLastInHierarchy())
            return false;

        return doesHierarchyContainANull(ParentObject.getNextInHierarchy());

    }


I liked the answer by Pontus Bremdahl, but added some more detail for my uses. Code:

    /// <summary>
    /// Get a member in an object hierarchy that might contain null references.
    /// </summary>
    /// <typeparam name="TSource"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="source">Base object to get member from.</param>
    /// <param name="getResult">Member path.</param>
    /// <param name="defaultResult">Returned object if object hierarchy is null.</param>
    /// <returns>Default of requested member type.</returns>
    public TResult SafeGet<TSource, TResult>(TSource source, Func<TSource, TResult> getResult, TResult defaultResult)
    {
        // Use EqualityComparer because TSource could by a primitive type.
        if (EqualityComparer<TSource>.Default.Equals(source, default(TSource)))
            return defaultResult;
        try
        {
            return getResult(source);
        }
        catch
        {
            return defaultResult;
        }
    }
    /// <summary>
    /// Get a member in an object hierarchy that might contain null references.
    /// </summary>
    /// <typeparam name="TSource"></typeparam>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="source">Base object to get member from.</param>
    /// <param name="getResult">Member path.</param>
    /// <returns>Default of requested member type.</returns>
    public TResult SafeGet<TSource, TResult>(TSource source, Func<TSource, TResult> getResult)
    {
        // Use EqualityComparer because TSource could by a primitive type.
        if (EqualityComparer<TSource>.Default.Equals(source, default(TSource)))
            return default(TResult);
        try
        {
            return getResult(source);
        }
        catch
        {
            return default(TResult);
        }
    }

Usage:

// Only authenticated users can run this code
if (!HttpContext.Current.SafeGet(s => s.User.Identity.IsAuthenticated))
        return;

// Get count limit from app.config
var countLimit = int.Parse(ConfigurationManager.AppSettings.SafeGet(
    s => s.Get("countLimit"),
      "100" // Default 100 if no value is present
    ));

// Is int 6 a class? Always no, but just to show primitive type usage.
var is6AClass = 6.SafeGet(i => i.GetType().IsClass);

Update

CSharp version 6 now has this built into it. https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6#null-conditional-operators

Null-conditional operators

Sometimes code tends to drown a bit in null-checking. The null-conditional operator lets you access members and elements only when the receiver is not-null, providing a null result otherwise:

int? length = customers?.Length; // null if customers is null
Customer first = customers?[0];  // null if customers is null

The null-conditional operator is conveniently used together with the null coalescing operator ??:

int length = customers?.Length ?? 0; // 0 if customers is null

The null-conditional operator exhibits short-circuiting behavior, where an immediately following chain of member accesses, element accesses and invocations will only be executed if the original receiver was not null:

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

This example is essentially equivalent to:

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

Except that customers is only evaluated once. None of the member accesses, element accesses and invocations immediately following the ? are executed unless customers has a non-null value.

Of course null-conditional operators can themselves be chained, in case there is a need to check for null more than once in a chain:

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

Note that an invocation (a parenthesized argument list) cannot immediately follow the ? operator – that would lead to too many syntactic ambiguities. Thus, the straightforward way of calling a delegate only if it’s there does not work. However, you can do it via the Invoke method on the delegate:

if (predicate?.Invoke(e) ?? false) { … }

We expect that a very common use of this pattern will be for triggering events:

PropertyChanged?.Invoke(this, args);

This is an easy and thread-safe way to check for null before you trigger an event. The reason it’s thread-safe is that the feature evaluates the left-hand side only once, and keeps it in a temporary variable.


CheckForNull(MyType element)
{
    if(element.ChildElement != null)
    {
        CheckForNull(element.ChildElement);
    }
    else
    {
        Console.WriteLine("Null Found");
    }
}


You need a recursive function to iterate through the structure and check each node and its children for null. I was working on a sample but IE crashed (typical!!). Will post one later.

Sample

You could do something as simple (assuming you only ever want to just check if the structure is valid) as this:

public void ValidateStructure()
{
    Node root = // get the root node
    try
    {
        ValidateChildren(root);
        Console.WriteLine("All nodes are valid");
    }
    catch (NullReferenceException)
    {
        Console.WriteLine("Structure contained a null node.");
    }

}

public void ValidateChildren(Node parent)
{
    // an NullReferenceException would be raised here since parent would be null 
    foreach (var child in parent.Children)
    {
        ValidateChildren(child);
    }
}


My solution would be something like:

public static TResult SafeGet<TSource, TResult>(this TSource source, Func<TSource, TResult> getResult) {
    if (source == null)
        return default(TResult);
    try {
        return getResult(source);
    }
    catch {
        return default(TResult);
    }
}

Usage:

Test myTestObject = null;
var myStringOrNull = myTestObject.SafeGet(x => x.test.test.test.mySring);


Use the Null monad. It can be in the same file or a different file so long as you using it.

public static class NullMonad {
    public static TResult SelectMany<TIn, TOut, TResult>(this TIn @in, Func<TIn, TOut> remainder, Func<TIn, TOut, TResult> resultSelector)
        where TIn : class
        where TOut : class
        where TResult : class {
        var @out = @in != null ? remainder(@in) : null;
        return @out != null ? resultSelector(@in, @out) : null;
    }
}

Then you can use LINQ:

var salary = from form in applicationForm
             from info in form.employeeInfo
             from cond in info.workingConditions
             select cond.salary

This will return the salary if it exists, or null if any of the prior statements result to null, without throwing an exception.

Is it that much better? No, just saves you the slightest bit of repetition, but it looks cool. It also avoids the overhead of creating all the unused "OrDefault" objects in the accepted answer.

0

精彩评论

暂无评论...
验证码 换一张
取 消