使用 T 类型的反射为具有属性的属性创建 Expression<Func<T, TValue>>

Posted

技术标签:

【中文标题】使用 T 类型的反射为具有属性的属性创建 Expression<Func<T, TValue>>【英文标题】:Using Reflection on Type T to create Expression<Func<T, TValue>> for Properties that have attributes 【发布时间】:2021-07-29 00:18:39 【问题描述】:

我目前正在探索封装一些通常是迭代的逻辑的可行性。使用第 3 方库,它具有通用 html Helper 方法,允许您将属性从 Generic T 类映射到表组件。通常你必须写一些东西来达到以下效果:

HtmlHelper.GenerateTable<ClassName>
.configure(tableDefinition =>
  // Adding each property you want to render here
  tableDefinition.add(myClassRef => myClassRef.propertyA);
  tableDefinition.add(myClassRef => myClassRef.propertyB);
)

我正在探索将属性添加到标准 Display 属性等属性的想法,然后使用反射将该属性添加到容器中。 add 方法只接受 Expression&lt;Func&lt;T, TValue&gt;&gt; 类型的参数。根据我目前对反射的理解,我知道我可以通过循环 PropertyInfo 并使用 GetCustomAttribute 检查我想要的属性来识别适用的属性。

我感到困惑的是,是否可以使用反射来提供 add 方法所期望的参数类型?

我将抛​​出到目前为止我已经开始使用的辅助方法逻辑。我的假设是,这将引导我走向 Expression 和 Lambda 类,但我无法充实任何有效的东西,因为我在技术上没有 TValue


var t = typeof(T);
List<PropertyInfo> properties = t.GetProperties().ToList();
foreach(var prop in properties)

    var attr = (DisplayAttribute[])prop.GetCustomAttributes(typeof(DisplayAttribute), false);
    if (attr.Length > 0)
    
        // TODO: Call add with expression?
    

【问题讨论】:

【参考方案1】:

您可以使用Expression class 上的方法轻松实现此目的。首先,使用 lambdas 编写表达式是最简单的(例如Expression&lt;Func&lt;A, B&gt;&gt; expr = x =&gt; x.PropertyA),然后在调试器中检查它以查看编译器构造了什么。

在你的情况下,这样的事情应该可以工作:

// The parameter passed into the expression (myClassRef) in your code
var parameter = Expression.Parameter(typeof(T), "myClassRef");

// Access the property described by the PropertyInfo 'prop' on the
// myClassRef parameter
var propertyAccess = Expression.Property(parameter, prop);
// Since we're returning an 'object', we'll need to make sure we box value types.
var box = Expression.Convert(propertyAccess, typeof(object));

// Construct the whole lambda
var lambda = Expression.Lambda<Func<T, object>>(box, parameter);
tableDefinition.add(lambda);

请注意,我将Expression&lt;Func&lt;T, object&gt;&gt; 传递给add,而不是Expression&lt;Func&lt;T, TValue&gt;&gt;。我猜测这无关紧要,它避免了使用反射调用add。当我过去编写类似于add 的方法时,我根本不关心TValue:我只是检查Expression 并从属性访问中获取PropertyInfo

如果您确实需要传递 Expression&lt;Func&lt;T, TValue&gt;&gt;,则必须执行以下操作:

var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
var lambda = Expression.Lambda(delegateType, propertyAccess, parameter);

// I'm assuming that your `add` method looks like:
// void add<T, TValue>(Expression<Func<T, TValue>> expr)
// I'm also assuming there's only one method called 'add' -- be smarter in that
// 'GetMethod' call if not.
var addMethod = typeof(TableDefinition)
    .GetMethod("add")
    .MakeGenericMethod(typeof(T), prop.PropertyType);

addMethod.Invoke(tableDefinition, new object[]  lambda );

请注意,在这种情况下您不需要 Expression.Convert(..., typeof(object))

【讨论】:

当我只处理字符串时,我确认这确实适用于库。但我正在测试的属性之一是 Nullable,它会在运行时将 Nullable 转换为对象时抛出异常。 Nullable 类型会指向通用方法方法,还是仍然可以使用最初提出的解决方案来解决? 为了给我之前的语句提供更多的上下文,抛出的异常是“System.Nullable`1[System.Double]”类型的表达式不能用于返回类型“System.Object”。这是有道理的,但是对于 Nullables 来说,如何更改语句并不是很清楚。 啊,你需要添加Expression.Convert(..., typeof(object))。请参阅我的编辑。您的第三方方法完全有可能不知道如何解析它,因此您可能需要退回到第二种方法,但试一试 我在玩游戏时碰巧偶然发现了它,它可以解决可空类型。即使在转换之后,DateTime 的行为也不像其他可为空的值类型,因此我最终明确地检测到 DateTime 并创建了一个转换 DateTime 的特殊规则,因此这可能是我能提出的唯一剩余改进领域。 这很奇怪——你有更多的信息吗?它似乎在这里工作正常:dotnetfiddle.net/4r3vaI

以上是关于使用 T 类型的反射为具有属性的属性创建 Expression<Func<T, TValue>>的主要内容,如果未能解决你的问题,请参考以下文章

根据反射获取属性信息并创建DataTable

如何使用反射在 Task<T> 中获取 T 类型的属性?

怎么通过反射获得实体类中List类型的对象的各个属性?

T 类型的泛型,其中 T 具有特定属性

C#反射实例学习及注意内容

反射怎么获取类属性类型