【问题标题】:Lookup property in object graph via a string通过字符串在对象图中查找属性
【发布时间】:2011-08-18 03:33:08
【问题描述】:

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

给定以下(人为的)类:

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

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

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

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

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

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

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

【问题讨论】:

  • 你认为你为什么需要这样做?
  • @Steve - 因为我需要控制任意类型的投影,而配置是最好的地方。
  • 这对于实现通用数据绑定机制也很有用 - BindingSource 的 DataMember 属性接受这样的导航路径字符串。

标签: c# reflection


【解决方案1】:

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

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

【讨论】:

  • 这可能更接近我正在寻找的无代码方法!
  • @Khanzor,嗯,这和你的方法完全一样,我仍然无法理解你已经有了工作答案,你在寻找什么替代方案?在性能方面还是其他方面?反射是唯一的方法,否则还有另一种生成动态方法并使用它的替代方法,但是对于小问题它的编码太多。
  • 请注意,您必须参考 - System.Web.dll
  • @Maxim:还要注意你必须放弃.NET Framework 4 Client Profilemsdn.microsoft.com/en-us/library/cc656912.aspx
【解决方案2】:

我过去也遇到过类似的事情。我选择了 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 );
  }

【讨论】:

  • 最大的原因是表达式利用了反射没有的属性的AST。唯一更快的方法是使用 Reflection.Emit 并使用 IL 编写它,但这似乎比它的价值更麻烦。请注意,如果您可以兑现 .Compile() 步骤生成的委托,这将有助于缩短执行时间。您需要利用缓存的属性和类型来检索您想要调用的委托。
  • 我认为这篇文章可能有助于进一步解释这一点,因为我不确定我是否做得很好。 stackoverflow.com/questions/2697655/…
  • 我喜欢你的回答,但它不支持数组或列表的属性。所以我稍微修改一下以支持 [index]:
【解决方案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;
}

【讨论】:

  • 为什么非递归实现一定更好?不过,GetProperty 方法是一个很好的提示。
  • @Khanzor:递归阻止我们在传入的 IEnumerable 上使用自然的 foreach 迭代器。这就是 foreach 的用途!
  • 好吧,我想我只是为了递归而使用递归。不过,我不认为选择列表并没有太大的问题。不过我同意你的观点,CLR 不进行尾递归,因此使用 foreach 实现更有意义:)。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

    这是基于 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 );
                   }
    
               }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-05
      • 1970-01-01
      相关资源
      最近更新 更多