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.