Skip to content

Instantly share code, notes, and snippets.

@idavis
Created July 6, 2011 22:33
Show Gist options
  • Save idavis/1068512 to your computer and use it in GitHub Desktop.
Save idavis/1068512 to your computer and use it in GitHub Desktop.
Inverse null coalescing 'operator' support. Only properties are allowed.
#region Using Directives
using System.Threading;
#endregion
namespace ObjectExtensions.Tests
{
public class Person
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string StreetName { get; set; }
public ZipCode ZipCode { get; set; }
}
public class ZipCode
{
public int Body { get; set; }
public int? Suffix { get; set; }
public Zone Zone { get; set; }
public Zone GetZone()
{
return Zone;
}
}
public class Zone
{
public string Name { get; set; }
}
}
#region Using Directives
using System;
using System.Linq.Expressions;
using System.Reflection;
#endregion
namespace ObjectExtensions
{
/// <summary>
/// implement null-safe dereferencing 'operator' (safe navigation operator)
/// </summary>
public static class ObjectExtensions
{
public static TValue ValueOrDefault<TSource, TValue>( this TSource instance,
Expression<Func<TSource, TValue>> expression )
{
return ValueOrDefault( instance, expression, true );
}
private static TValue ValueOrDefault<TSource, TValue>( this TSource instance,
Expression<Func<TSource, TValue>> expression,
bool nested )
{
return ReferenceEquals( instance, default( TSource ) )
? default( TValue )
: nested
? EvaluateExpression( instance, expression )
: expression.Compile()( instance );
}
internal static TProperty EvaluateExpression<TSource, TProperty>( TSource source,
Expression<Func<TSource, TProperty>>
expression )
{
var method = expression.Body as MethodCallExpression;
if ( method != null )
{
return ValueOrDefault( source, expression, false );
}
var body = expression.Body as MemberExpression;
if ( body == null )
{
const string format = "Expression '{0}' must refer to a property.";
string message = string.Format( format, expression );
throw new ArgumentException( message );
}
object value = EvaluateMemberExpression( source, body );
if ( ReferenceEquals( value, null ) )
{
return default( TProperty );
}
return (TProperty) value;
}
private static object EvaluateMemberExpression( object instance, MemberExpression memberExpression )
{
if ( memberExpression == null )
{
return instance;
}
instance = EvaluateMemberExpression( instance, memberExpression.Expression as MemberExpression );
var propertyInfo = memberExpression.Member as PropertyInfo;
instance = ValueOrDefault( instance, item => propertyInfo.GetValue( item, null ), false );
return instance;
}
}
}
#region Using Directives
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endregion
namespace ObjectExtensions.Tests
{
[TestClass]
public class ObjectExtensionTests
{
[TestMethod]
public void when_the_accessed_object_is_null_then_null_is_returned()
{
Person person = null;
string result = person.ValueOrDefault( p => p.Name );
Assert.AreEqual( null, result );
}
[TestMethod]
public void when_the_accessed_property_is_null_then_null_is_returned()
{
var person = new Person { Name = null };
string value = person.ValueOrDefault( p => p.Name );
Assert.AreEqual( null, value );
}
[TestMethod]
public void when_the_accessed_property_is_not_null_then_its_value_is_returned()
{
const string name = "Ian";
var person = new Person { Name = name };
string value = person.ValueOrDefault( p => p.Name );
Assert.AreEqual( name, value );
}
[TestMethod]
public void when_a_complex_property_is_null_then_null_is_returned()
{
var person = new Person { Address = null };
Address value = person.ValueOrDefault( p => p.Address );
Assert.AreEqual( null, value );
}
[TestMethod]
public void EvaluateExpression_non_nested_properties_are_evaluated()
{
const string name = "Ian";
var person = new Person { Name = name };
string value = ObjectExtensions.EvaluateExpression( person, p => p.Name );
Assert.AreEqual( name, value );
}
[TestMethod]
public void EvaluateExpression_nested_properties_are_evaluated()
{
const string name = "Ian";
var person = new Person { Address = new Address { StreetName = name } };
string value = ObjectExtensions.EvaluateExpression( person, p => p.Address.StreetName );
Assert.AreEqual( name, value );
}
[TestMethod]
public void when_a_nested_accessed_property_is_null_then_null_is_returned()
{
var person = new Person { Address = null };
string value = person.ValueOrDefault( p => p.Address.StreetName );
Assert.AreEqual( null, value );
}
[TestMethod]
public void when_a_nested_accessed_property_is_not_null_then_its_value_is_returned()
{
const string name = "Ian";
var person = new Person { Address = new Address { StreetName = name } };
string value = person.ValueOrDefault( p => p.Address.StreetName );
Assert.AreEqual( name, value );
}
[TestMethod]
public void when_a_double_nested_accessed_propertys_parent_is_null_then_defaultT_is_returned()
{
var person = new Person { Address = null };
int value = person.ValueOrDefault( p => p.Address.ZipCode.Body );
Assert.AreEqual( 0, value );
}
[TestMethod]
public void when_a_double_nested_accessed_propertys_parent_is_null_then_defaultT_is_returned_for_nullable_types()
{
var person = new Person { Address = null };
int? value = person.ValueOrDefault( p => p.Address.ZipCode.Suffix );
Assert.AreEqual( null, value );
}
[TestMethod]
public void when_a_double_nested_accessed_property_is_not_null_then_its_value_is_returned()
{
const int body = 99016;
var person = new Person { Address = new Address { ZipCode = new ZipCode { Body = body } } };
int value = person.ValueOrDefault( p => p.Address.ZipCode.Body );
Assert.AreEqual( body, value );
}
[TestMethod]
public void when_a_triple_nested_accessed_propertys_parent_is_null_then_null_is_returned()
{
var person = new Person { Address = null };
string value = person.ValueOrDefault( p => p.Address.ZipCode.Zone.Name );
Assert.AreEqual( (object) null, value );
}
[TestMethod]
public void when_a_triple_nested_accessed_property_is_not_null_then_its_value_is_returned()
{
const string name = "Yay";
var person = new Person
{ Address = new Address { ZipCode = new ZipCode { Zone = new Zone { Name = name } } } };
string value = person.ValueOrDefault( p => p.Address.ZipCode.Zone.Name );
Assert.AreEqual( name, value );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment