2

Let's assume that I have these dummy classes defined:

public class MyObject
{
    public InterestingFact InterestingFact { get; set; }
}
public class InterestingFact
{
    public Detail Detail { get; set; }
}
public class Detail
{
    public string Information { get; set; }
}

I also have an instance of MyObject:

var obj = new MyObject() { InterestingFact = new InterestingFact() };

If I want to try to access the Information property, I could do something like this:

string info = null;
if (obj != null
    && obj.InterestingFact != null
    && obj.InterestingFact.Detail != null)
{
    info = obj.InterestingFact.Detail.Information;
}

Ignoring, for the sake of this discussion, the Law of Demeter, this is not a whole lot of fun every time I want to access something deep within an object I have. I can, of course, create an extension method:

public static U NullOr<T, U>(this T target, Func<T, U> selector)
{
    if (EqualityComparer<T>.Default.Equals(target, default(T)))
    {
        return default(U);
    }
    return selector(target);
}

This makes selecting the data a little easier:

// Here I have a null of type string.
var info = obj.NullOr(o => o.InterestingFact).NullOr(f => f.Detail).NullOr(d => d.Information);

However, this is still a little long winded. Ideally, I'd like to do something more like this:

// Here I should still have a null of type string.
var info = obj.NullOr(o => o.InterestingFact.Detail.Information);

I've never worked with Expressions; if I take in an Expression<Func<T, U>> instead of the Func<T, U> in NullOr, it looks to me like it is one member access rather than three. Is there a way to approach the above, or is this formulation out of reach?

(There's also, of course, the concern with the last fomulation that the expression sent in could be something other than just chained member access..)

zimdanen
  • 5,508
  • 7
  • 44
  • 89
  • 2
    This is [proposed](https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c) as an addition to C# using the `?.` operator, but it has not yet made it into the language. Mads says they're seriously considering it, so one day you may be able to write `obj?.InterestingFact?.Detail?.Information`. Until then, the way you're doing it is the only way I know of. – Moshe Katz Apr 18 '14 at 03:30
  • @MosheKatz: I've seen that, and boy have I wished I could make it with operator overloading. – zimdanen Apr 18 '14 at 03:30
  • 3
    possible duplicate of [How do I break down a chain of member access expressions?](http://stackoverflow.com/questions/11108254/how-do-i-break-down-a-chain-of-member-access-expressions) – Moshe Katz Apr 18 '14 at 03:34
  • @MosheKatz: Thanks! I don't know how I missed that. – zimdanen Apr 18 '14 at 03:37

1 Answers1

0

About the simplest way you can achieve the desired result with an extension method is the following.

public static class ExtensionMethods
{
    public static TR DefaultIfNull<T, TR>(this T source, Expression<Func<T, TR>> expr, TR defaultValue = default(TR))
    {
        TR result = defaultValue;

        try
        {
            result = expr.Compile().Invoke(source);
        }
        catch (NullReferenceException)
        {
            // DO NOTHING
        }

        return result;
    }
}

And here is some example usages of the above

var info1 = obj.DefaultIfNull(x => x.InterestingFact.ToString(), "Null1");
var info2 = obj.DefaultIfNull(x => x.InterestingFact.Detail.ToString(), "Null2");
var info3 = obj.DefaultIfNull(x => x.InterestingFact.Detail.Information);

Note that this is not the BEST solution as it doesn't check individual expression nodes for null, so if any node in the tree happens to internally generate an NullReferenceException then the default value would be returned instead of the exception being thrown. However, for general and simple usage this is probably the optimal solution (mostly for its simplicity).

pjs
  • 2,601
  • 1
  • 20
  • 24
  • I should also note that there isn't really any benefit here to using the expression tree parser. You can simplify my code above even further by just invoking the Func directly. – pjs Aug 20 '14 at 15:14