In Effective Extensions – Extending Enums in C# we covered how to use a few useful methods to extend Enums in C#. This time we are going to look at Extending Models in C#.
UPDATED 8/29/2014: Added a few more GetPropertyName overloads as well as GetPropertyNames.
Our first method is our initial GetPropertyName which retrieves a property name from a simple expression:
/// <summary>
/// Gets the property name from an expression.
/// </summary>
/// <param name="expression">The expression to evaluate.</param>
/// <returns></returns>
public static string GetPropertyName(this Expression expression)
{
// Get property name
switch (expression.NodeType)
{
case ExpressionType.Call:
var callExp = expression as MethodCallExpression;
if (callExp == null || !callExp.Arguments.Any()) return "";
// Build body on first argument
var callBody = callExp.Arguments.First() as MemberExpression;
// Get property name
return callBody == null ? "" : callBody.Member.Name;
break;
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
var andOrExp = expression as BinaryExpression;
if (andOrExp == null) return "";
// Build body on last argument
var andOrBody = andOrExp.Right as MemberExpression;
// Get property name
return andOrBody == null ? "" : andOrBody.Member.Name;
break;
default:
// Member
var body = expression as MemberExpression;
// Body found
if (body != null) return body.Member.Name;
// Get body
var ubody = (UnaryExpression)expression;
body = ubody.Operand as MemberExpression;
return (body != null) ? body.Member.Name : "";
break;
}
}
The goal of GetPropertyName is to find the relevant MemberExpression and get the property name associated with it. This is not always a simple task and different Expression types have to be evaluated in unique ways.
Let’s break this down into each case:
- The first block deals with a MethodCallExpression. You typically see these with expressions like this:
x => x.MyCollection.Contains(5). We need to mine this down to the first argument to get the MemberExpression. - The second and third blocks deal with complex expressions using && (AndAlso) or || (OrElse) between 2 or more statements. An example might be like this: x => x.IsActive && x.MyValue == 5. The Right value will hold the right-most, singular value as a MemberExpression, where the Left value will hold the expression less the Right. So, to properly take an expression like this apart, you would need to iterate through until Left is just a MemberExpression.
- The last block is a simple MemberExpression evaluation. When all else fails, go this route. Sometimes, casting our expression as a MemberExpression won’t work, so we attempt to cast it as a UnaryExpression and cast it’s Operand property as a MemberExpression.
Now, let’s look at some GetPropertyName overloads that will use our initial method to evaluate complex expressions:
/// <summary>
/// Gets the property name from an expression.
/// Example: this would be used for expressions in the format: "() => Name"
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression">The expression to evaluate.</param>
/// <returns></returns>
public static string GetPropertyName<T>(this Expression<Func<T>> expression)
{
return expression.Body.GetPropertyName();
}
/// <summary>
/// Gets the property name from an expression.
/// Example: this would be used for expressions in the format: "() => IsActive"
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression">The expression.</param>
/// <returns></returns>
public static string GetPropertyName<T>(this Expression<Func<T, bool>> expression)
{
return expression.Body.GetPropertyName();
}
/// <summary>
/// Gets the property name from an expression.
/// Example: this would be used for expressions in the format: "x => x.Name"
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="expression">The exp.</param>
/// <returns></returns>
public static string GetPropertyName<T, TProperty>(this Expression<Func<T, TProperty>> expression)
{
return expression.Body.GetPropertyName();
}
- The first method retrieves a property name from a simple expression such as: () => Name.
- The second method retrieves a property name from a boolean expression such as: () => IsActive.
- The third method retrieves a property name from a property-based expression such as: x => x.Name.
Q: Okay, so how do we get all the property names of a multi-statement expression that uses && or ||?
A: In order to do that, we need to do a little more work and return an array.
Let’s look at our follow-up method GetPropertyNames:
/// <summary>
/// Gets multiple property names from a complex expression.
/// Example: this would be used for expressions in the format: "x => x.Name && x.IsActive"
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression">The expression.</param>
/// <returns></returns>
public static IList<string> GetPropertyNames<T>(this Expression<Func<T, bool>> expression)
{
var names = new List<string>();
// Body
var expBody = expression.Body;
// Get property name
if (expBody is BinaryExpression)
{
// If a complex expression, we need to disassemble it from Right to Left,
// Or, we simply need to get the property from the Left.
if (expBody.IsComplexBinaryExpression())
{
names.AddRange(expBody.GetPropertiesFromBinary());
}
else
{
names.Add(expBody.GetPropertyNameFromSimpleBinaryExpression());
}
}
else
{
// Get a singular property name
names.Add(expression.GetPropertyName());
}
// Return unique list
return names.GroupBy(x => x).Select(y => y.Key).ToList();
}
/// <summary>
/// Gets the properties from binary.
/// </summary>
/// <param name="expression">The expression.</param>
/// <returns></returns>
private static IEnumerable<string> GetPropertiesFromBinary(this Expression expression)
{
var names = new List<string>();
var currentExp = expression;
while (currentExp is BinaryExpression)
{
// Cast expression
var exp = (currentExp as BinaryExpression);
// Body
if (exp.IsComplexBinaryExpression())
{
// If "Right" is a BinaryExpression, get it's components, or
// Just get the property name of the "Right" expression.
var right = exp.Right as BinaryExpression;
if (right != null)
{
names.AddRange(right.GetPropertiesFromBinary());
}
else
{
names.Add(exp.Right.GetPropertyName());
}
}
else
{
// Add to list
names.Add(exp.GetPropertyNameFromSimpleBinaryExpression());
}
// Next expression
currentExp = exp.Left;
}
// Get last expression
names.Add(currentExp.GetPropertyName());
// Return
return names;
}
/// <summary>
/// Determines whether [is complex binary expression] [the specified exp].
/// </summary>
/// <param name="exp">The exp.</param>
/// <returns></returns>
private static bool IsComplexBinaryExpression(this Expression exp)
{
var expBody = exp as BinaryExpression;
if (expBody == null) return false;
// Evaluate
return (expBody.NodeType == ExpressionType.AndAlso || expBody.NodeType == ExpressionType.OrElse);
}
/// <summary>
/// Gets the property name from simple binary expression.
/// </summary>
/// <param name="exp">The exp.</param>
/// <returns></returns>
private static string GetPropertyNameFromSimpleBinaryExpression(this Expression exp)
{
var expBody = exp as BinaryExpression;
if (expBody == null) return "";
// Body
var body = expBody.Left as MemberExpression;
// Get property name
return (body != null) ? body.Member.Name : "";
}
Here, we are doing what we discussed earlier, which is iterating through our expression, storing the Right value on each pass until the Left is just a MemberExpression. The result is a list of all properties involved in the expression.
The next method we will evaluate is DeepCopy which completely clones a model through serialization:
/// <summary>
/// Deeps the copy.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="oSource">The o source.</param>
/// <returns></returns>
public static T DeepCopy<T>(this T oSource)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, oSource);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
As you can see, this serializes the source with a BinaryFormatter and injects it into a MemoryStream. This is considered the most effective way to clone an object.
Finally, we have an IsNull method that performs a Deep Null check, which means it inspects each element in an object tree to determine if it is valid:
/// <summary>
/// Checks if property or field and all parent instances if they are null.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent">The parent.</param>
/// <param name="lambda">The lambda expression.</param>
/// <returns><c>true</c> if null; otherwise, <c>false</c>.</returns>
/// <remarks>Only properties and/or fields are allowed for all parts of the expression.</remarks>
public static bool IsNull<T>(this object parent, Expression<Func<T>> lambda)
{
var memberParts = new List<MemberInfo>();
var expression = lambda.Body;
// ExpressionType.Constant occurs once we get to the parent
while (expression.NodeType != ExpressionType.Constant)
{
var memberExpression = (MemberExpression)expression;
memberParts.Add(memberExpression.Member);
expression = memberExpression.Expression;
}
// Parts are extracted in reverse order, so this corrects it
memberParts.Reverse();
// 'instance' keeps track of the instance associated with 'member' below
var instance = parent;
while (memberParts.Any())
{
// Set the current member for evaluation on this loop
var member = memberParts.First();
// Break down how this is evaluated by property vs. field
switch (member.MemberType)
{
case MemberTypes.Property:
var property = (PropertyInfo)member;
// Gets the value by invoking the property on the instance
object value = property.GetValue(instance, null);
// Compares value with null
if (value == null) return true;
// If not null, set 'instance' to the value for the next loop
instance = value;
break;
case MemberTypes.Field:
var field = (FieldInfo)member;
// Gets the value by v the field on the instance
value = field.GetValue(instance);
// Compares value with null
if (value == null) return true;
// If not null, set 'instance' to the value for the next loop
instance = value;
break;
default:
// Method type members, and others, are not supported
throw new InvalidOperationException("IsNull: MemberType must be Property or Field.");
}
// If not null, remove the first element for the next loop
memberParts.RemoveAt(0);
}
// No nulls were found
return false;
}
As demonstrated, this method inspects each element from greatest parent down until reaching our target element to ensure all elements in the tree are not null.
The usage would be like this:
// Why do this: var test = (parent == null || parent.MyProperty == null || parent.MyProperty.ChildProperty == null); // When you can do this: var test = parent.IsNull(()=> parent.MyProperty.ChildProperty); // Check // "test" is true if parent is null.
And that wraps this series on extensions.
Happy coding.
Pretty cool, but not correct in all cases:
– you check the parent like a field of the same parent.
– if you have some object like a ValueObject (DDD), that overrides Equals and GetHashCode, then the null check (value == null) is not correct. You need a real reference check (Object.ReferenceEquals(value, null).
My version of your solution:
public static bool IsNull(this object parent, Expression<Func> lambda)
{
if (!Object.ReferenceEquals(parent, null))
{
var memberParts = new List();
var expression = lambda.Body;
// ExpressionType.Constant occurs once we get to the parent
while (expression.NodeType != ExpressionType.Constant)
{
var memberExpression = (MemberExpression)expression;
memberParts.Add(memberExpression.Member);
expression = memberExpression.Expression;
}
// Parts are extracted in reverse order, so this corrects it
memberParts.Reverse();
memberParts.RemoveAt(0); // remove parent
// ‘instance’ keeps track of the instance associated with ‘member’ below
var instance = parent;
while (memberParts.Any())
{
// Set the current member for evaluation on this loop
var member = memberParts.First();
// Break down how this is evaluated by property vs. field
switch (member.MemberType)
{
case MemberTypes.Property:
var property = (PropertyInfo)member;
// Gets the value by invoking the property on the instance
object value = property.GetValue(instance, null);
// Compares value with null
//if (value == null)
if (Object.ReferenceEquals(value, null))
return true;
// If not null, set ‘instance’ to the value for the next loop
instance = value;
break;
case MemberTypes.Field:
var field = (FieldInfo)member;
// Gets the value by v the field on the instance
value = field.GetValue(instance);
// Compares value with null
// if (value == null)
if (Object.ReferenceEquals(value, null))
return true;
// If not null, set ‘instance’ to the value for the next loop
instance = value;
break;
default:
// Method type members, and others, are not supported
throw new InvalidOperationException(“IsNull: MemberType must be Property or Field.”);
}
// If not null, remove the first element for the next loop
memberParts.RemoveAt(0);
}
// No nulls were found
return false;
}
return true;
}
Great job.