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:
PropertyChanged(this, new PropertyChangedEventArgs("SomePropertyName"));
Obviously, this is error prone. When it comes to string
values, a typo can easily be made. 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:
var obj = new SampleObject();
var name1 = PropertyName.For(() => obj.SampleProperty);
Assert.AreEqual("SampleProperty", name1);
var name2 = PropertyName.For(() => this.SampleProperty);
Assert.AreEqual("SampleProperty", name2);
var name3 = PropertyName.For(() => SampleType.StaticProperty);
Assert.AreEqual("StaticProperty", name3);
var name4 = PropertyName.For<SampleType>(x => x.SampleProperty);
Assert.AreEqual("SampleProperty", name4);
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:
public sealed class PropertyName
{
private const string _ErrorMessage = "Expression '{0}' does not contain a property.";
public static string For<T>(Expression<Func<T,
object>> propertyExpression)
{
if (propertyExpression == null)
{
throw new ArgumentNullException("propertyExpression");
}
var body = propertyExpression.Body;
return GetPropertyName(body);
}
public static string For(Expression<Func<object>> propertyExpression)
{
if (propertyExpression == null)
{
throw new ArgumentNullException("propertyExpression");
}
var body = propertyExpression.Body;
return GetPropertyName(body);
}
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 the 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