In various scenarios, there is a need for referring to a property via a string containing its name. Probably the most common case is when invoking the PropertyChanged event handler:

// Notify that property value has changed.
PropertyChanged(this, new PropertyChangedEventArgs("SomePropertyName"));

Obviously this is error prone. When it comes to string values, a typo can easily be done. The solution to this problem is to get the property name automatically from the property reference. This is where lambda expressions become handy. () => Property or x => x.Property lets you perceive any faults during compile time or even earlier during design time.

To clarify, I’m not discovering anything new here, nor reinventing the wheel. For several years, the subject has been known, discussed, and implemented. While reviewing a few implementations I stumbled across, I decided to create my own variation, anyway to put together some features scattered around.

Usage

The PropertyName helper class can be used to retrieve the property name of static and non-static properties from object instances and types directly. It is also possible to get property names from nested properties. Some examples:

// Get the property name form object instance
var obj = new SampleObject();
var name1 = PropertyName.For(() => obj.SampleProperty);
Assert.AreEqual("SampleProperty", name1);

// Get the property name form within object instance
var name2 = PropertyName.For(() => this.SampleProperty);
Assert.AreEqual("SampleProperty", name2);

// Get the name of a static property
var name3 = PropertyName.For(() => SampleType.StaticProperty);
Assert.AreEqual("StaticProperty", name3);

// Get the property name from a type
var name4 = PropertyName.For<SampleType>(x => x.SampleProperty);
Assert.AreEqual("SampleProperty", name4);

// Get the name of a nested property from object instance and type
var name5 = PropertyName.For(() => this.SampleProperty.NestedProperty);
var name6 = PropertyName.For<SampleType>(x => x.SampleProperty.NestedProperty);
Assert.AreEqual("SampleProperty.NestedProperty", name5);
Assert.AreEqual("SampleProperty.NestedProperty", name6);

Source code

Below is the full code of the PropertyName helper class:

/// <summary>
/// Returns the name of the property using lambda expression.
/// </summary>
public sealed class PropertyName
{
	/// <summary>
	/// Error message in case lambda expression does not contain a property.
	/// </summary>
	private const string _ErrorMessage = "Expression '{0}' does not contain a property.";

	/// <summary>
	/// Returns the name of the property using lambda expression.
	/// </summary>
	/// <typeparam name="T">Type containing the property.</typeparam>
	/// <param name="propertyExpression">Expression containing the property.</param>
	/// <returns>The name of the property.</returns>
	public static string For<T>(Expression<Func<T, object>> propertyExpression)
	{
		if (propertyExpression == null)
		{
			throw new ArgumentNullException("propertyExpression");
		}
		var body = propertyExpression.Body;
		return GetPropertyName(body);
	}

	/// <summary>
	/// Returns the name of the property using lambda expression.
	/// </summary>
	/// <param name="propertyExpression">Expression containing the property.</param>
	/// <returns>The name of the property.</returns>
	public static string For(Expression<Func<object>> propertyExpression)
	{
		if (propertyExpression == null)
		{
			throw new ArgumentNullException("propertyExpression");
		}
		var body = propertyExpression.Body;
		return GetPropertyName(body);
	}

	/// <summary>
	/// Returns the name of the property using lambda expression.
	/// </summary>
	/// <param name="propertyExpression">Expression containing the property.</param>
	/// <param name="nested">Is it a recurrent invocation?</param>
	/// <returns>The name of the property.</returns>
	private static string GetPropertyName(Expression propertyExpression, bool nested = false)
	{
		MemberExpression memberExpression;

		if (propertyExpression is UnaryExpression)
		{
			var unaryExpression = (UnaryExpression)propertyExpression;
			memberExpression = unaryExpression.Operand as MemberExpression;
		}
		else
		{
			memberExpression = propertyExpression as MemberExpression;
		}

		if (memberExpression == null)
		{
			if (nested) return string.Empty;
			throw new ArgumentException(string.Format(_ErrorMessage, propertyExpression),
										"propertyExpression");
		}

		var propertyInfo = memberExpression.Member as PropertyInfo;
		if (propertyInfo == null)
		{
			if (nested) return string.Empty;
			throw new ArgumentException(string.Format(_ErrorMessage, propertyExpression),
										"propertyExpression");
		}

		return (memberExpression.Expression != null &&
				memberExpression.Expression.NodeType == ExpressionType.MemberAccess)
					? GetPropertyName(memberExpression.Expression, true) + propertyInfo.Name
					: propertyInfo.Name + (nested ? "." : string.Empty);
	}
}

Some light on the main code

Now, let us discuss the body of the GetPropertyName method. The expression containing a property of the reference type is of the MemberExpression type. Nothing unusual here. For a property of value type it is of UnaryExpression type, however. Its Operand member contains the value of MemberExpression that we are interested in. In other words, an expression containing a value type property is boxed and we need to unbox it.

Since there are other expression types that might accidentally sail in, we cannot explicitly cast an expression to MemberExpression but use as operator and check for nullity.

The propertyInfo block is just for ensuring that we are getting the name of a property. Generally, this part could be dropped and the class called MemberName.

Let’s move on to the return statement and recurrence that occurs here. It is designed to get the nested properties, e.g., x => x.SomeProperty.SomeSubProperty.OrEvenMore. The order of property names retrieval is bottom-up. The first chunk returned for the example above is OrEvenMore, then SomeSubProperty and SomeProperty, respectively.

The if (nested) return string.Empty; conditional statement is used to enable
() => obj.SomeProperty
The obj chunk fits the return statements conditions and GetPropertyName method is invoked recurrently. It may seem not to be the neatest solution, but it’s the best I came up with for the moment. Got any hints regarding this issue, please let me know.

References