Created
January 5, 2010 17:49
-
-
Save robertwilczynski/269553 to your computer and use it in GitHub Desktop.
Evaluating possible null reference expressions
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
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Linq.Expressions; | |
using System.Diagnostics; | |
namespace CallChainMagic | |
{ | |
public class CallChainMagic | |
{ | |
private static List<MemberExpression> SplitExpression(Expression<Func<object>> expression) | |
{ | |
List<MemberExpression> expressions = new List<MemberExpression>(); | |
MemberExpression memberExpression = null; | |
if (expression.Body.NodeType == ExpressionType.MemberAccess) | |
{ | |
memberExpression = expression.Body as MemberExpression; | |
} | |
expressions.Add(memberExpression); | |
while (memberExpression.Expression.NodeType == ExpressionType.MemberAccess) | |
{ | |
memberExpression = memberExpression.Expression as MemberExpression; | |
expressions.Add(memberExpression); | |
} | |
return expressions; | |
} | |
public static object TraverseExpression(Expression<Func<object>> expression, Func<MemberExpression, int, object, object> action) | |
{ | |
List<MemberExpression> expressions = SplitExpression(expression); | |
object output; | |
// Evaluating expression root value. | |
var ex = expressions[expressions.Count - 1]; | |
var del = Expression<Func<object>>.Lambda(ex).Compile(); | |
output = del.DynamicInvoke(); | |
if (output == null) | |
{ | |
throw new NullReferenceException( | |
String.Format("Null reference enountered on '{0}'.", ex.Member.Name)); | |
} | |
for (int i = expressions.Count - 2; i >= 0; i--) | |
{ | |
//Console.WriteLine(ex.Member.Name); | |
ex = expressions[i]; | |
output = action(ex, i, output); | |
} | |
return output; | |
} | |
public static object Eval(Expression<Func<object>> expression) | |
{ | |
return TraverseExpression(expression, (ex, index, o) => | |
{ | |
var output = (ex.Member as PropertyInfo).GetValue(o, new object[] { }); | |
if (output == null && index > 0) | |
{ | |
throw new NullReferenceException( | |
String.Format("Null reference enountered on '{0}'.", ex.Member.Name)); | |
} | |
return output; | |
}); | |
} | |
} | |
class Program | |
{ | |
private const int Iterations = 1000; | |
private static void Regular(Order order) | |
{ | |
if (order == null) | |
{ | |
throw new NullReferenceException(""); | |
} | |
if (order.Customer == null) | |
{ | |
throw new NullReferenceException(""); | |
} | |
if (order.Customer.ContactInfo == null) | |
{ | |
throw new NullReferenceException(""); | |
} | |
if (order.Customer.ContactInfo.Address == null) | |
{ | |
throw new NullReferenceException(""); | |
} | |
} | |
private static void Expressions(Order order) | |
{ | |
CallChainMagic.Eval(() => order.Customer.ContactInfo.Address.Line1); | |
} | |
public static void MeasureTime(string name, Action action, int iterations) | |
{ | |
Stopwatch watch = new Stopwatch(); | |
watch.Start(); | |
for (int i = 0; i < iterations; i++) | |
{ | |
try | |
{ | |
action(); | |
} | |
catch | |
{ | |
} | |
} | |
watch.Stop(); | |
Console.WriteLine("{0} took {1} (average {2} ms).", name, watch.Elapsed, watch.Elapsed.TotalMilliseconds / (double)iterations); | |
} | |
private static void PreformanceTest() | |
{ | |
Console.WriteLine("Performance test"); | |
Console.WriteLine("============================================================"); | |
Order order = new Order(); | |
Console.WriteLine("order.Customer == null"); | |
MeasureTime("Regular", () => Regular(order), Iterations); | |
MeasureTime("Expressions", () => Expressions(order), Iterations); | |
order.Customer = new Customer(); | |
Console.WriteLine("order.Customer.ContactInfo == null"); | |
MeasureTime("Regular", () => Regular(order), Iterations); | |
MeasureTime("Expressions", () => Expressions(order), Iterations); | |
order.Customer.ContactInfo = new ContactInfo(); | |
Console.WriteLine("order.Customer.ContactInfo.Address == null"); | |
MeasureTime("Regular", () => Regular(order), Iterations); | |
MeasureTime("Expressions", () => Expressions(order), Iterations); | |
order.Customer.ContactInfo.Address = new Address(); | |
Console.WriteLine("order.Customer.ContactInfo.Address != null"); | |
MeasureTime("Regular", () => Regular(order), Iterations); | |
MeasureTime("Expressions", () => Expressions(order), Iterations); | |
} | |
private static void ValueTest() | |
{ | |
Console.WriteLine("Value test"); | |
Console.WriteLine("============================================================"); | |
Order order = new Order(); | |
order.Customer = new Customer(); | |
order.Customer.ContactInfo = new ContactInfo(); | |
order.Customer.ContactInfo.Address = new Address() { Line1 = "test list" }; | |
var value = CallChainMagic.Eval(() => order.Customer.ContactInfo.Address.Line1); | |
Console.WriteLine("Expecting '{0}'", order.Customer.ContactInfo.Address.Line1); | |
Console.WriteLine("Value of 'order.Customer.ContactInfo.Address.Line1' is '{0}'", value); | |
} | |
static void Main(string[] args) | |
{ | |
ValueTest(); | |
Console.WriteLine(); Console.WriteLine(); | |
PreformanceTest(); | |
} | |
} | |
public class Order | |
{ | |
public Customer Customer { get; set; } | |
} | |
public class Customer | |
{ | |
public ContactInfo ContactInfo { get; set; } | |
} | |
public class ContactInfo | |
{ | |
public Address Address { get; set; } | |
} | |
public class Address | |
{ | |
public string Line1 { get; set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment