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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | /// <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | /// <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | /// <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /// <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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | /// <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:
1 2 3 4 5 6 7 8 | // 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.