Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lookup property in object graph via a string

Tags:

c#

reflection

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));
}
like image 307
Khanzor Avatar asked May 04 '11 00:05

Khanzor


4 Answers

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

object result = DataBinder.Eval(myPerson, "PersonsAddress.HousePhone.Number");
like image 190
Simon Mourier Avatar answered Nov 05 '22 21:11

Simon Mourier


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 );
  }
like image 20
Brian Dishaw Avatar answered Nov 05 '22 22:11

Brian Dishaw


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;
}
like image 3
Rick Sladkey Avatar answered Nov 05 '22 20:11

Rick Sladkey


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.

like image 1
Jamie Thomas Avatar answered Nov 05 '22 20:11

Jamie Thomas