通过字符串在对象图中查找属性

时间:2011-05-04 00:31:01

标签: c# reflection

我正在尝试使用任意字符串访问嵌套类结构的各个部分。

鉴于以下(人为)课程:

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

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

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

我希望能够从"PersonsAddress.HousePhone.Number"对象的实例获取Person对象。

目前我正在使用反射进行一些时髦的递归查找,但我希望那里的一些忍者有更好的想法。

作为参考,这是我开发的(糟糕的)方法:

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));
}

5 个答案:

答案 0 :(得分:13)

您可以简单地使用标准.NET DataBinder.Eval Method,如下所示:

object result = DataBinder.Eval(myPerson, "PersonsAddress.HousePhone.Number");

答案 1 :(得分:4)

我过去曾经有过类似的东西。我使用lambda方法,因为在编译它们之后我可以缓存它们。我在此代码中删除了缓存。

我包含了一些单元测试来显示该方法的用法。我希望这有用。

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 );
  }

答案 2 :(得分:3)

这是一个非递归版本,具有(几乎)相同的语义:

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 :(得分:1)

由于您已经对解析字符串属性路径感兴趣,因此您可以通过查看Scott Guthrie @ Microsoft作为示例发布的Dynamic LINQ查询库获益。它解析你的字符串表达式并生成可以按照@Brian Dishaw的建议编译和缓存的表达式树。

通过提供可在配置方法中使用的简单而强大的表达式语法,这将为您提供大量其他选项。它支持可枚举的常见LINQ方法,以及简单的运算符逻辑,数学计算,属性路径评估等。

答案 4 :(得分:0)

这是基于Brian的代码,进行了一些修改以支持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 );
               }

           }