6
\$\begingroup\$

I am always super annoyed when I have to write a bunch of if statements whenever I have to traverse an object graph of possibly null objects. As there is not yet a ?. operator I resorted to creating this wonderful piece of work: TryGetProperty. Please tear it apart. This was written for LINQPad.

public bool TryGetProperty<TInstance, TFinal>(TInstance instance, Expression<Func<TInstance, TFinal>> propertyAccessor, out TFinal output)
{

    var currentExpression = propertyAccessor.Body as MemberExpression;
    var stack = new Stack<PropertyInfo>();
    do
    {
       stack.Push(currentExpression.Member as PropertyInfo);
    } while((currentExpression = currentExpression.Expression as MemberExpression) != null);

    var failed = false;

    object currentObject = instance;
    while(!failed && stack.Count > 0 )
    {
        currentObject = stack.Pop().GetMethod.Invoke(currentObject, null);
        failed = currentObject == null && stack.Count != 0;
    }

    output = failed ? default(TFinal) : (TFinal) currentObject;
    return !failed;
}

Example call from the same file.

void Main()
{  
    List<Class1> instances = new List<Class1> 
    {
        new Class1(),
        new Class1
        {
            Class2 = new Class2
            {
                Class3 = new Class3
                {
                    AProperty = "not null"
                }
            }
        },
        new Class1
        {
            Class2 = new Class2
            {
                Class3 = new Class3()
            }
        }
    };

    string propertyToGet;

    foreach(var instance in instances) 
    {
       if(TryGetProperty(instance, c => c.Class2.Class3.AProperty, out propertyToGet) && propertyToGet != null)
       {
            "Yay!".Dump();
       }
       else
       {
            "Boo!".Dump();
       }
    }
}

public class Class1
{
    public Class2 Class2{ get; set; }
}

public class Class2
{
    public Class3 Class3 { get; set; }
}
public class Class3
{
    public string AProperty { get; set; }
}
\$\endgroup\$
  • 6
    \$\begingroup\$ There is a ?. operator for C# 6.0. :) \$\endgroup\$ – Der Kommissar Aug 20 '15 at 23:06
6
\$\begingroup\$
public bool TryGetProperty<TInstance, TFinal>(TInstance instance, Expression<Func<TInstance, TFinal>> propertyAccessor, out TFinal output)
{

    var currentExpression = propertyAccessor.Body as MemberExpression;
    var stack = new Stack<PropertyInfo>();
    do
    {
       stack.Push(currentExpression.Member as PropertyInfo);
    } while((currentExpression = currentExpression.Expression as MemberExpression) != null);

    var failed = false;

    object currentObject = instance;
    while(!failed && stack.Count > 0 )
    {
        currentObject = stack.Pop().GetMethod.Invoke(currentObject, null);
        failed = currentObject == null && stack.Count != 0;
    }

    output = failed ? default(TFinal) : (TFinal) currentObject;
    return !failed;
}  

This will throw an ArgumentNullException at var currentExpression = propertyAccessor.Body as MemberExpression; if propertyAccessor is null.

It will also throw the same exception if propertyAccessor.Body != MemberExpression because you are accessing the Member property here stack.Push(currentExpression.Member as PropertyInfo);.

If you use the as soft cast, you should always check the result against null.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Yeah, I figured the thing shouldn't be very forgiving if you abused it. I could do some preemptive checking though \$\endgroup\$ – moarboilerplate Aug 21 '15 at 12:30
8
\$\begingroup\$

I tend to use monads in such situations:

public static TResult With<TSource, TResult>(this TSource source, Func<TSource, TResult> action)
    where TSource : class
{
    if (source == null) return default(TResult);
    return action(source);
}

public static TSource Do<TSource>(this TSource source, Action<TSource> action)
    where TSource : class
{
    if (source != null)
    {
        action(source);
    }
    return source;
}

And then:

c.With(x => x.Class2).With(x => x.Class3).With(x => x.AProperty).Do(x => "Yay!".Dump());
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I like With if it's on a builder class and not an extension on every reference type. However, I think I could do without Do. In order to make your code behave the same as my code using Do, you'd have to resort to continuation-passing, e.g. (TSource source, Action<TSource> success, Action<TSource> failed). So I guess it depends on whether your context demands a functional approach or not. \$\endgroup\$ – moarboilerplate Aug 21 '15 at 13:59
  • 1
    \$\begingroup\$ @moarboilerplate Or, you could write something like an ElseDo method that calls the Action on the inverse condition, rather than having to add responsibility to Do. \$\endgroup\$ – cbojar Aug 21 '15 at 20:21
  • \$\begingroup\$ A state monad need only be responsible for the state and the result of evaluating the state (i.e. the actual property value). It would be far simpler to have the final method just return the actual value instead of also having it executing delegates based on evaluating the evaluation of the state. \$\endgroup\$ – moarboilerplate Aug 21 '15 at 21:37
  • \$\begingroup\$ (and writing ElseDo would still be passing continuations, just in separate methods) \$\endgroup\$ – moarboilerplate Aug 21 '15 at 21:40

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.