【问题标题】:Creating functions to retrieve values of properties retrieved via reflection创建函数来检索通过反射检索到的属性值
【发布时间】:2017-01-28 17:06:14
【问题描述】:

我正在编写代码以将我的 ORM 实体的数据传输到数据集中。因为我不想为定义需要写下哪些属性的每种类型编写特殊代码,所以我目前正在使用反射(在实体类型上调用 GetProperties,为此类型构建 DataTable,然后在每个 Propertyinfo 上调用 GetValue每个实体)。现状:可以,但是速度很慢。

现在我正在尝试构建一个方法,该方法返回一个函数以快速检索某些属性的值,但我在这里遇到了困难。这是我到目前为止得到的:

  /// <summary>
  /// creates a func that will return the value of the given property 
  /// </summary>
  /// <typeparam name="T">type of the entity</typeparam>
  /// <param name="propertyInfo">the property to get the value from</param>
  /// <returns>a function accepting an instance of T and returning the value of the property</returns>
  private Func<T, object> CreateGetPropertyFunc<T>(PropertyInfo propertyInfo)
   {         
     MethodInfo getMethod = propertyInfo.GetGetMethod();
     return (Func<T, object>)Delegate.CreateDelegate(typeof(Func<T, object>), getMethod);          
  }

这些是我的单元测试:

  [TestMethod]
  public void TestGenerateDelegate()
  {
     var employee = new Employee
     {            
        Id = 1,
        Name = "TestEmployee",            
     };

     Func<Employee, object> getIdValueFunc = CreateGetPropertyFunc<Employee>(typeof(Employee).GetProperty("Id"));
     Assert.AreEqual(1, getIdValueFunc(employee));
  }

    [TestMethod]
    public void TestGenerateDelegateName()
    {
        var employee = new Employee
        {
            Name = "Test"
        };

        Func<Employee, object> getNameValueFunc = CreateGetPropertyFunc<Employee>(typeof(Employee).GetProperty("Name"));
        Assert.AreEqual("Test", getNameValueFunc(employee));
    }

当我调用第一个时,会引发 ArgumentException,其中包含消息“绑定到目标方法时出现异常”(已翻译,可能是不同的文本)。第二个测试反而通过了。

我很确定我没有正确处理 CreateDelegate 方法。谁能指点我正确的方向,好吗?

更新:

正如 PetSerAI 所说,这似乎是方差的问题,值原始类型不能通过 CreateDelegate 作为对象返回...

【问题讨论】:

  • 委托返回类型差异不适用于值类型。您不能将返回 int 的方法绑定到返回 object 的委托。
  • 你是对的,它使用 Name 属性,但不适用于 Id。真可惜!

标签: c# reflection


【解决方案1】:

您可以使用表达式树来动态创建引用指定属性的委托:

private Func<T, object> CreateGetPropertyFunc<T>(PropertyInfo propertyInfo) {
    ParameterExpression p = Expression.Parameter(typeof(T));
    return Expression.Lambda<Func<T, object>>(
        Expression.Convert(Expression.Property(p, propertyInfo), typeof(object)),
        p
    ).Compile();
}

【讨论】:

    【解决方案2】:

    CreateDelegate(Type, MethodInfo) 绑定到静态方法(您没有静态方法,这就是您收到错误的原因)

    你只能使用Delegate.CreateDelegate这个版本的实例方法:CreateDelegate(Type, Object, MethodInfo)

    创建一个代表指定类型的委托 静态或实例方法,带有指定的第一个参数。

    https://msdn.microsoft.com/en-us/library/system.delegate.createdelegate(v=vs.110).aspx

    根据 PetSerAl 的评论,您应该将“null”作为“第一个参数”传递,以创建一个“开放委托”,您将向其中传递实例。

        // In this case, the delegate has one more 
        // argument than the instance method; this argument comes
        // at the beginning, and represents the hidden instance
        // argument of the instance method. Use delegate type D1
        // with instance method M1.
        //
        d1 = (D1) Delegate.CreateDelegate(typeof(D1), null, mi1);
    
        // An instance of C must be passed in each time the 
        // delegate is invoked.
        //
        d1(c1, "Hello, World!");
    

    【讨论】:

    • CreateDelegate(Type, MethodInfo) 可以从非静态方法创建委托:Delegate.CreateDelegate(typeof(Func&lt;object, string&gt;), typeof(object).GetMethod("ToString"))Docs 说:在.NET Framework 2.0 版本中,这个方法重载也可以创建开放实例方法委托;也就是说,明确提供实例方法隐藏的第一个参数的委托。
    • 谢谢,我没有意识到这一点——所以他需要使用另一个重载。我会适当地编辑我的答案。
    【解决方案3】:

    为什么不一路通用,让编译器为你创建委托?

    public static class Ext
    {
        public static Func<T, TProp> CreateGetPropertyFunc<T, TProp>(this T obj, Func<T, TProp> func)
        {
            return func;
        }
    }
    

    然后:

    var getterForId = employee.CreateGetPropertyFunc(x => x.Id);
    int result = getterForId(employee);
    // result can now be 'int' and doesn't have to be 'object'
    

    如果您没有实际的 T 实例,或者只是不想要扩展方法:

    public Func<T, object> CreateGetPropertyFunc<T>(Func<T, object> func)
    {
        return func;
    }
    

    然后:

    var getterForId = CreateGetPropertyFunc<Employee>(x => x.Id);
    object result = getterForId(employee);
    // result must be 'object' (unless explicitly casted)
    

    (前者在性能方面更好,因为像int 这样的值类型不会被装箱,并且类型安全性得以保留)

    【讨论】:

    • 可能是我误解了你。我不想为我拥有的所有实体属性写所有这些行;我想通过反射动态获取一个类型的所有属性并创建快速访问方法来提取属性值。写一个 lambda "x=> x.Id" 来获取... id 有什么意义?
    • @Udontknow,“编写 Lambda 的要点”是编译器将其转换为您想要的 Func&lt;&gt;,而您不必使用反射。另外 1) 它在性能方面更好,2) 它在定义和实现上都更短,最重要的是 3) 它保留了类型安全。
    • 我确实了解 lambdas 的优点,但我感觉我没有清楚地描述我的目标。我在 DataSet 上有一个扩展方法,并且希望能够将任何类型(!)的任何对象的数据传输到 DataTable 实例。如何为我在设计时不知道的属性编写 lambda?那就是反射进来的地方。:-)
    • @Udontknow,您的问题已经使用了CreateGetPropertyFunc&lt;T&gt; 的通用版本,我只是对其进行了扩展。如果您需要获取未知类型的属性获取器,请更改您的问题以反映这一点(以便我们了解您当前的方法是什么)。
    • 我不明白为什么使用泛型类型参数意味着了解现有属性?我以为我用“我不想为定义需要写下哪些属性的每种类型编写特殊代码”清楚地写了它......无论如何,我会尝试澄清它。
    【解决方案4】:

    你可以调用你的getMethod:

    private Func<T, object> CreateGetPropertyFunc<T>(PropertyInfo propertyInfo)
    {
        MethodInfo getMethod = propertyInfo.GetGetMethod();
        return o => getMethod.Invoke(o, BindingFlags.Default, null, null, CultureInfo.CurrentCulture);
    }
    

    【讨论】:

    • 这可能是我们德国人所说的“危险的半知半解”,但是与直接调用相比,通过 methodinfo 调用不是很慢吗?这就是我想要避免的......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-12
    • 1970-01-01
    • 2010-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-23
    相关资源
    最近更新 更多