Monday, October 19, 2015

Explanation of the GetPropertyName Function

I was recently asked how a particular GetPropertyName function my team was using worked. This is what I said:

Usage Example

C#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static void Main(string[] args)
{
    var demoClass = new Demo();
    Console.WriteLine("demoClass.{0}: {1}", 
        Utilities.GetPropertyName(() => demoClass.TestProperty),
        demoClass.TestProperty);
    Console.WriteLine("demoClass.{0}: {1}", 
        Utilities.GetPropertyName(() => demoClass.TestNumber),
        demoClass.TestNumber);
}

VB.NET

1
2
3
4
5
6
7
8
9
Sub Main()
    Dim demoClass As New Demo()
    Console.WriteLine("demoClass.{0}: {1}",
                      GetPropertyName(Function() demoClass.TestProperty),
                      demoClass.TestProperty)
    Console.WriteLine("demoClass.{0}: {1}",
                      GetPropertyName(Function() demoClass.TestNumber),
                      demoClass.TestNumber)
End Sub

Usage Explanation

The first line prints of the method prints the name of the demoClass’s TestProperty property and its value. The second line prints the name of the demoClass’s TestNumber property and its value.

GetPropertyName Function Overview

The GetPropertyName function returns a string.

The GetPropertyName function takes a single input argument which must be a lambda expression. The lambda expression must be a function. The function must contain a single statement that returns the value of one of the object’s properties.

The GetPropertyName function uses built-in .NET framework functionality to get the name of the property that was accessed by the lambda expression and returns that property name as a string.

More information about lambda expressions:

GetPropertyName Code

C#

1
2
3
4
public static string GetPropertyName<T>(Expression<Func<T>> expression)
{
    return ((MemberExpression)expression.Body).Member.Name;
}
  • The <T> in GetPropertyName<T> is used to genericize the class that is used in the lambda expression.
  • The Expression<Func<T>> in (Expression<Func<T>> expression) indicates the input argument to the GetPropertyName function must be a lambda expression that is a function that uses an object of type “T”. Expression is a special .NET Framework datatype that causes the compiler to create and populate a data structure that contains details about the code inside a lambda expression. More information about this data type can be found at http://msdn.microsoft.com/en-us/library/Bb335710.aspx https://docs.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression-1.
  • expression.Body gets the body of the lambda expression.
  • ((MemberExpression)expression.Body) casts the body of the lambda expression to a MemberExpression. A MemberExpression is a datatype that represents a class’ property being accessed. If the lambda expression passed into the GetPropertyName function is not a single line of code that accesses a class property, an InvalidCastException will be thrown at runtime.
  • The Member property of the MemberExpression datatype represents the property being accessed. It is of type MemberInfo.
  • The Name property of the MemberInfo type returns the name of the property as a string.

VB.NET

1
2
3
Public Function GetPropertyName(Of T)(expression As Expression(Of Func(Of T))) As String
    Return DirectCast(expression.Body, MemberExpression).Member.Name
End Function
  • The (Of T) in GetPropertyName(Of T) is used to genericize the class that is used in the lambda expression.
  • The Expression(Of Func(Of T)) in (expression As Expression(Of Func(Of T))) indicates the input argument to the GetPropertyName function must be a lambda expression that is a function that uses an object of type “T”. Expression is a special .NET Framework datatype that causes the compiler to create and populate a data structure that contains details about the code inside a lambda expression. More information about this data type can be found at http://msdn.microsoft.com/en-us/library/Bb335710.aspx https://docs.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression-1.
  • expression.Body gets the body of the lambda expression.
  • DirectCast(expression.Body, MemberExpression) casts the body of the lambda expression to a MemberExpression. A MemberExpression is a datatype that represents a class’ property being accessed. If the lambda expression passed into the GetPropertyName function is not a single line of code that accesses a class property, an InvalidCastException will be thrown at runtime.
  • The Member property of the MemberExpression datatype represents the property being accessed. It is of type MemberInfo.
  • The Name property of the MemberInfo type returns the name of the property as a string.

Why are we using GetPropertyName?

So why do we use GetPropertyName instead of just passing in a string constant (see alternate code below)?

C#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static void AlternateVersion()
{
    var demoClass = new Demo();
    Console.WriteLine("demoClass.{0}: {1}",
        "TestProperty",
        demoClass.TestProperty);
    Console.WriteLine("demoClass.{0}: {1}",
        "TestNumber",
        demoClass.TestNumber);
}

VB.NET

1
2
3
4
5
6
7
8
9
Sub AlternateVersion()
    Dim demoClass As New Demo()
    Console.WriteLine("demoClass.{0}: {1}",
                      "TestProperty",
                      demoClass.TestProperty)
    Console.WriteLine("demoClass.{0}: {1}",
                      "TestNumber",
                      demoClass.TestNumber)
End Sub

If the name of a demoClass’s property changes, the developer needs to remember to update the code to use the new name. The string is not tied to the class at all. One alternative would be to add readonly properties to the class that returned the name of the properties (e.g. Demo.TestPropertyName or something like that), but the developer would still need to remember to update that property’s name if its associated property’s name changed.

By using the GetPropertyName function, if a property is renamed, Resharper can automatically rename our GetPropertyName lambda expressions. If we are not using Resharper and we rename a property, we will get a compiler error if we forget to update the property's name wherever it is used.