Created
July 6, 2011 22:33
-
-
Save idavis/1068512 to your computer and use it in GitHub Desktop.
Inverse null coalescing 'operator' support. Only properties are allowed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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