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开发者_如何学C 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));
}
You could simply use the standard .NET DataBinder.Eval Method, like this:
object result = DataBinder.Eval(myPerson, "PersonsAddress.HousePhone.Number");
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 );
}
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;
}
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.
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 );
}
}
精彩评论