9

I'm trying to access various parts of a nested class structure using a arbitrary string.

Given the following (contrived) classes:

public class Person
{
   public Address PersonsAddress { get; set; }
}

public class Adddress
{
   public PhoneNumber HousePhone { get; set; }
}

public class PhoneNumber
{
   public string Number { get; set; }
}

I'd like to be able to get the object at "PersonsAddress.HousePhone.Number" from an instance of the Person object.

Currently I'm doing some funky recursive lookup using reflection, but I'm hoping that some ninjas out there have some better ideas.

For reference, here is the (crappy) method I've developed:

private static object ObjectFromString(object basePoint, IEnumerable<string> pathToSearch)
{
   var numberOfPaths = pathToSearch.Count();

   if (numberOfPaths == 0)
     return null;

   var type = basePoint.GetType();
   var properties = type.GetProperties();

   var currentPath = pathToSearch.First();

   var propertyInfo = properties.FirstOrDefault(prop => prop.Name == currentPath);

   if (propertyInfo == null)
     return null;

   var property = propertyInfo.GetValue(basePoint, null);

   if (numberOfPaths == 1)
     return property;

   return ObjectFromString(property, pathToSearch.Skip(1));
}
3
  • Why do you think you need to do this? Commented May 4, 2011 at 2:11
  • @Steve - Because I need to control projection of arbitrary types, and configuration is the best place for that. Commented May 4, 2011 at 2:19
  • This is also useful for implementing a generic data binding mechanism - DataMember property of BindingSource accepts a navigation path string like that. Commented Jan 29, 2013 at 20:01

5 Answers 5

15
+25

You could simply use the standard .NET DataBinder.Eval Method, like this:

object result = DataBinder.Eval(myPerson, "PersonsAddress.HousePhone.Number");
Sign up to request clarification or add additional context in comments.

4 Comments

This is probably closer to the no-code approach I was looking for!
@Khanzor, well it is exactly same as your method, I am still unable to understand that you already have working answer, what alternative you are looking for? In terms of performance or anything else? Reflection is the only way, otherwise there is another alternative of generating dynamic method and use it, but its too much of coding for small issue.
Just note that you'll be have to reference - System.Web.dll
@Maxim: Note also that you have to give up the .NET Framework 4 Client Profile: msdn.microsoft.com/en-us/library/cc656912.aspx
5

I've had to some something similar in the past. I went with the lambda approach because after compiling them I can cache them. I've removed the caching in this code.

I included a few unit tests to show the usage of the method. I hope this is helpful.

private static object GetValueForPropertyOrField( object objectThatContainsPropertyName, IEnumerable<string> properties )
  {
     foreach ( var property in properties )
     {
        Type typeOfCurrentObject = objectThatContainsPropertyName.GetType();

        var parameterExpression = Expression.Parameter( typeOfCurrentObject, "obj" );
        Expression memberExpression = Expression.PropertyOrField( parameterExpression, property );

        var expression = Expression.Lambda( Expression.GetDelegateType( typeOfCurrentObject, memberExpression.Type ), memberExpression, parameterExpression ).Compile();

        objectThatContainsPropertyName = expression.DynamicInvoke( objectThatContainsPropertyName );
     }

     return objectThatContainsPropertyName;
  }

  [TestMethod]
  public void TestOneProperty()
  {
     var dateTime = new DateTime();

     var result = GetValueForPropertyOrField( dateTime, new[] { "Day" } );

     Assert.AreEqual( dateTime.Day, result );
  }

  [TestMethod]
  public void TestNestedProperties()
  {
     var dateTime = new DateTime();

     var result = GetValueForPropertyOrField( dateTime,  new[] { "Date", "Day" } );

     Assert.AreEqual( dateTime.Date.Day, result );
  }

  [TestMethod]
  public void TestDifferentNestedProperties()
  {
     var dateTime = new DateTime();

     var result = GetValueForPropertyOrField( dateTime, new[] { "Date", "DayOfWeek" } );

     Assert.AreEqual( dateTime.Date.DayOfWeek, result );
  }

3 Comments

The biggest reason is that Expressions utilize AST of the properties where reflection does not. The only faster way to do this would be to use Reflection.Emit and write it using IL, but that seems like it would be more trouble than it's worth. Please note that if you can cashe the delegate generated by the .Compile() step, it will help keep execution time low. You'll need to leverate the property as well as the type for the cache to retrieve your delegate that you want to invoke.
I think this post might help further explain this as I'm not sure I did a good job. stackoverflow.com/questions/2697655/…
I like your answer, but it didn't support property of array or list. so I modify it a little bit to support [index]:
3

Here's a non-recursive version with (almost) the same semantics:

private static object ObjectFromString(object basePoint, IEnumerable<string> pathToSearch)
{
    var value = basePoint;
    foreach (var propertyName in pathToSearch)
    {
        var property = value.GetType().GetProperty(propertyName);
        if (property == null) return null;
        value = property.GetValue(value, null);
    }
    return value;
}

3 Comments

Why is a non-recursive implementation necessarily better? The GetProperty method is a good tip though.
@Khanzor: Recursion prevents us from using the natural foreach iterator on the incoming IEnumerable. That's what foreach is for!
well, I guess I am just using recursion for recursion's sake. There's not really that much wrong with list chomping though, I don't think. I take your point though, the CLR doesn't do tail recursion, so it makes more sense to implement using foreach :).
1

Since you are already interested in resolving string property paths, you may benefit from looking into the Dynamic LINQ query library posted as an example by Scott Guthrie @ Microsoft. It parses your string expressions and produces express trees that can be compiled and cached as suggested by @Brian Dishaw.

This would provide you with a wealth of additional options by providing a simple and robust expression syntax you can use in your configuration approach. It supports the common LINQ methods on enumerables, plus simple operator logic, math calculations, property path evaluation, etc.

Comments

0

this is based on Brian's code, did some modification to support index addressing for List:

private static object GetValueForPropertyOrField( object objectThatContainsPropertyName, IEnumerable<string> properties )
       {
           foreach ( var property in properties )
           {
               Type typeOfCurrentObject = objectThatContainsPropertyName.GetType();

               var parameterExpression = Expression.Parameter( typeOfCurrentObject, "obj" );
               var arrayIndex = property.IndexOf('[');
               if ( arrayIndex > 0)
               {
                   var property1 = property.Substring(0, arrayIndex);
                   Expression memberExpression1 = Expression.PropertyOrField( parameterExpression, property1 );
                   var expression1 = Expression.Lambda( Expression.GetDelegateType( typeOfCurrentObject, memberExpression1.Type ), memberExpression1, parameterExpression ).Compile();
                   objectThatContainsPropertyName = expression1.DynamicInvoke( objectThatContainsPropertyName );
                   var index = Int32.Parse(property.Substring(arrayIndex+1, property.Length-arrayIndex-2));
                   typeOfCurrentObject = objectThatContainsPropertyName.GetType(); 
                   parameterExpression = Expression.Parameter( typeOfCurrentObject, "list" );
                   Expression memberExpression2 =  Expression.Call(parameterExpression, typeOfCurrentObject.GetMethod("get_Item"), new Expression[] {Expression.Constant(index)});
                   var expression2 = Expression.Lambda( Expression.GetDelegateType( typeOfCurrentObject, memberExpression2.Type ), memberExpression2, parameterExpression ).Compile();
                    objectThatContainsPropertyName = expression2.DynamicInvoke( objectThatContainsPropertyName );
               }
               else
               {
                   Expression memberExpression = Expression.PropertyOrField( parameterExpression, property );  
                   var expression = Expression.Lambda( Expression.GetDelegateType( typeOfCurrentObject, memberExpression.Type ), memberExpression, parameterExpression ).Compile(); 
                   objectThatContainsPropertyName = expression.DynamicInvoke( objectThatContainsPropertyName );
               }

           }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.