How To Reference A Property By Name Without Using String Constants

by Jon Davis 13. June 2010 02:39

I’m working on some code here where I’m loading an entity using a dictionary. I’m not using .NET 4.0 so I’m not going to get to use the dynamic keyword and its IDictionary implementation of properties reflection.

My original code looked something sort of like this:

public void Populate(IDictionary<string, object> values) 
{
foreach (var kvp in values) {
var key = kvp.Key;
var value = kvp.Value;
switch (key) 
{
case "FirstName":
this.FirstName = value;
break;
case "LastName":
this.LastName = value;
break;
// and so on and so forth
}
}
}

This looks sloppy. First of all, I don’t want to have to declare each and every property again for its setter just to support IDictionary. Secondly, what if I rename my properties? Then I’d have to go back and update this switch…case statement again. I want to use a strongly-typed property reference, so that if I rename the property I can use the IDE’s built-in refactoring functionality with which all uses of that property are automatically updated. I don’t get that benefit if I’m using string constants.

I’d come across some interesting tutorials in the past that addressed this problem, and I thought I’d give it my own go based on their insight. My revised solution is not by any means original. (Nothing I ever do is, I suppose.)

To make a long story short, I flexed some recently learned LINQ expression muscle on a member evaluator expression to obtain the strongly typed property’s name. In my implementation (not entirely shown here) I actually do need to verify the property by name because I have a bunch of setter logic that I don’t expose here, such as replacing the value being an IEnumerable<string> with logic that clears an ObservableCollection<string> and populates it with the value. Anyway, here’s the basic flow:

public override void Populate(IDictionary<string, object> values)
{
foreach (var kvp in values) {
if (key == base.ResolvePropertyName(() => this.SpecialProperty))
{
// this.SpecialProperty = value;
// do something special with setter here
}
else if (key == base.ResolvePropertyName(() => this.AnotherSpecialProperty))
{
// this.AnotherSpecialProperty = value;
// do something special with setter here
}
else 
{
base.Populate(key, value);
}
}
}
/// and in the base class ....
/// <summary>
/// Returns the name of the property. Syntax:
/// <code>
/// var propertyName = ResolvePropertyName(() =&gt; this.MyProperty)
/// </code>
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
protected string ResolvePropertyName(Expression<Func<T>> e)
{
if (e.NodeType == ExpressionType.Lambda)
{
return ResolvePropertyName(((LambdaExpression)e).Body);
}
if (e.NodeType == ExpressionType.MemberAccess)
{
return ((MemberExpression)e).Member.Name;
}
throw new InvalidOperationException("Given expression is not type MemberAccess.");
}
private static Dictionary<Type, List<PropertyInfo>> TypeProperties
= new Dictionary<Type, List<PropertyInfo>>();
/// <summary>
/// Populates a property of the current entity with the the given key/value pair.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Populate(string property, object value)
{
var t = this.GetType();
List<PropertyInfo> pis;
if (!TypeProperties.ContainsKey(t))
{
TypeProperties[t] = t.GetProperties().ToList();
}
pis = TypeProperties[t];
var pi = pis.Find(pif => pif.Name == property);
if (pi == null) throw new KeyNotFoundException("Property does not exist: \"" + property + "\"");
pi.SetValue(this, value, null);
}

 

The Populate(key,value) method is basic .NET 2.0 reflection with some sorta-kinda basic caching.  The method above it is using LINQ expressions to extract the member access invocation as expressed in the LINQ statement.

I wrote a unit test to invoke this and stepped through it with the debugger. Seems to work fine.

After further tweaking, I migrated the base class functions to a utility class so that these functions can be used with anything, not just with objects that inherit a common base class. I also improved the reflection caching of Populate() so that the properties are properly hashed on their property names.

public static class ObjectUtility
{
/// <summary>
/// Returns the name of the property. Syntax:
/// <code>
/// var propertyName = obj.ResolvePropertyName(() =&gt; obj.MyProperty)
/// </code>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <param name="expr"></param>
/// <returns></returns>
public static string ResolvePropertyName<T>(this object obj, Expression<Func<T>> expr)
{
return ResolvePropertyName(expr);
}
/// <summary>
/// Returns the name of the property. Syntax:
/// <code>
/// var propertyName = ObjectUtility.ResolvePropertyName(() =&gt; obj.MyProperty)
/// </code>
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public static string ResolvePropertyName(Expression e)
{
if (e.NodeType == ExpressionType.Lambda)
{
return ResolvePropertyName(((LambdaExpression)e).Body);
}
if (e.NodeType == ExpressionType.MemberAccess)
{
return ((MemberExpression)e).Member.Name;
}
throw new InvalidOperationException("Given expression is not type MemberAccess.");
}
private static Dictionary<Type, Dictionary<string, PropertyInfo>> TypeProperties
= new Dictionary<Type, Dictionary<string, PropertyInfo>>();
/// <summary>
/// Populates a property of an object with the the given key/value pair.
/// </summary>
/// <param name="obj"></param>
/// <param name="property"></param>
/// <param name="value"></param>
public static void Populate(this object obj, string property, object value)
{
var t = obj.GetType();
if (!TypeProperties.ContainsKey(t))
{
TypeProperties[t] = new Dictionary<string, PropertyInfo>();
var lst = t.GetProperties().ToList();
foreach (var item in lst)
{
if (!TypeProperties[t].ContainsKey(item.Name))
{
TypeProperties[t][item.Name] = item;
}
}
}
var pisdic = TypeProperties[t];
var pi = pisdic.ContainsKey(property) ? pisdic[property] : null;
if (pi == null) throw new KeyNotFoundException("Property does not exist: \"" + property + "\"");
pi.SetValue(obj, value, null);
}
}

 

 

Comments

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading




 

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

About the author

Jon Davis (aka "stimpy77") has been a programmer, developer, and consultant for web and Windows software solutions professionally since 1997, with experience ranging from OS and hardware support to DHTML programming to IIS/ASP web apps to Java network programming to Visual Basic applications to C# desktop apps.
 
Software in all forms is also his sole hobby, whether playing PC games or tinkering with programming them. "I was playing Defender on the Commodore 64," he reminisces, "when I decided at the age of 12 or so that I want to be a computer programmer when I grow up."

Jon was previously employed as a senior .NET developer at a very well-known Internet services company whom you're more likely than not to have directly done business with. However, this blog and all of jondavis.net have no affiliation with, and are not representative of, his former employer in any way.

Contact Me 


Tag cloud

Calendar

<<  April 2014  >>
MoTuWeThFrSaSu
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar