如何使用 lambda 表达式和匿名类型获取类型的属性名称?
Posted
技术标签:
【中文标题】如何使用 lambda 表达式和匿名类型获取类型的属性名称?【英文标题】:How do I get property names of a type using a lambda expression and anonymous type? 【发布时间】:2012-04-12 13:29:40 【问题描述】:我正在尝试使用表达式树和匿名类型来实现以下目标。
假设我有这个课程:
class Person
public string FirstName get;set;
public string MiddleName get;set;
public string LastName get;set;
public DateTime DateOfBirth get;set;
现在我希望能够调用以下内容:
string[] names = Foo<Person>(x=> new x.LastName, x.DateOfBirth );
我希望名称包含 2 个项目,“LastName”和“DateOfBirth”。
我正在尝试以编译时安全的方式扩展 PetaPoco,而不是编写字符串 sql,以便我可以指定要包含在 SQL 中的属性/列的列表,而不是选择所有内容。我有一些相当大的实体,在某些情况下,出于性能原因,我不想选择所有列。
【问题讨论】:
除了学习练习之外,还有什么特别的理由来实现您自己的 ORM 实用程序吗?那里有许多 ORM 供应商,而且通常那些本土的供应商带来的麻烦超过了它们的价值。 我刚刚更新了这个问题。我实际上试图扩展 PetaPoco 以支持 SELECT 中的指定列,而不是数据类上的所有属性。 【参考方案1】:试试这个尺寸:
public static string[] Foo<T, TResult>(Expression<Func<T, TResult>> func)
return typeof(TResult).GetProperties().Select(pi => pi.Name).ToArray();
当你从你的 lamda 返回一个匿名类型时,你可以遍历这个匿名类型的所有属性并使用属性的推断名称。然而,当使用这个时,语法会更像:
Foo((Person x) => new x.LastName, x.DateOfBirth );
这是因为第二个泛型参数是匿名类型。
【讨论】:
感谢您的回答。我喜欢它的简洁。 @Brady:你最终扩展了 Petapoco 吗?如果是这样,您是否为它创建了一个分支?我对您的解决方案感兴趣。Foo((Person x) => x.FirstName)
返回什么?它返回一个包含这些元素的字符串数组: "Chars", "Length"
。这意味着必须使用匿名类型调用Foo
,即使对于单个属性也是如此。请看我的回答。【参考方案2】:
我很懒,所以这段代码只处理公共属性。但这应该是您入门的良好基础。
public static string[] Foo<T>(Expression<Func<T, object>> func)
var properties = func.Body.Type.GetProperties();
return typeof(T).GetProperties()
.Where(p => properties.Any(x => p.Name == x.Name))
.Select(p =>
var attr = (ColumnAttribute) p.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault();
return (attr != null ? attr.Name : p.Name);
).ToArray();
【讨论】:
【参考方案3】:此处给出的答案适用于仅选择单个属性或选择多个属性时。它们都不适合两者。在撰写我的答案时,Lukazoid 的 answer 仅适用于多个属性,其余的适用于单个属性。
下面的代码考虑了这两种情况,也就是说,您可以使用它来选择单个属性和多个属性。请注意,我没有在此处添加任何健全性检查,因此请随意添加您自己的。
string[] Foo<T>(Expression<Func<Person, T>> func)
if (func.Body is NewExpression)
// expression selects multiple properties,
// OR, single property but as an anonymous object
// extract property names right from the expression itself
return (func.Body as NewExpression).Members.Select(m => m.Name).ToArray();
// Or, simply using reflection, as shown by Lukazoid
// return typeof(T).GetProperties().Select(p => p.Name).ToArray();
else
// expression selects only a single property of Person,
// and not as an anonymous object.
return new string[] (func.Body as MemberExpression).Member.Name ;
或者更简洁地说,使用三元运算符,这一切都变成了这样:
string[] Foo<T>(Expression<Func<Person, T>> func)
return (func.Body as NewExpression) != null
? typeof(T).GetProperties().Select(p => p.Name).ToArray()
: new string[] (func.Body as MemberExpression).Member.Name ;
下载 LinkPad 文件:LinkPad 在线查看:Repl.it
请随时指出我可能遗漏的任何内容。
【讨论】:
【参考方案4】:一页代码就是一千个字,下面是微软在Prism中的做法:
///<summary>
/// Provides support for extracting property information based on a property expression.
///</summary>
public static class PropertySupport
/// <summary>
/// Extracts the property name from a property expression.
/// </summary>
/// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
/// <param name="propertyExpression">The property expression (e.g. p => p.PropertyName)</param>
/// <returns>The name of the property.</returns>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="propertyExpression"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when the expression is:<br/>
/// Not a <see cref="MemberExpression"/><br/>
/// The <see cref="MemberExpression"/> does not represent a property.<br/>
/// Or, the property is static.
/// </exception>
public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
if (propertyExpression == null)
throw new ArgumentNullException("propertyExpression");
var memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException(Resources.PropertySupport_NotMemberAccessExpression_Exception, "propertyExpression");
var property = memberExpression.Member as PropertyInfo;
if (property == null)
throw new ArgumentException(Resources.PropertySupport_ExpressionNotProperty_Exception, "propertyExpression");
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
throw new ArgumentException(Resources.PropertySupport_StaticExpression_Exception, "propertyExpression");
return memberExpression.Member.Name;
如果您想考虑属性,它会稍微复杂一些,但接受Expression<Func<T>>
并找出目标属性的名称的总体思路是相同的。
更新: 按原样,该方法将只接受一个参数;我只提供它作为指导。这个想法当然可以概括:
public static string[] ExtractPropertyNames<T>(
Expression<Func<T, object>> propertyExpression)
此方法将接受一个接受 T 并返回匿名类型的表达式,然后您可以对其进行反思。您可以将第二个类型参数替换为 object
,但这在这里并没有真正做任何事情,因为您唯一想做的就是反映类型。
【讨论】:
这不只支持一个成员(即 x=>x.LastName)吗? 你是对的,它只支持单个成员。此外,该单个成员不得作为匿名对象。看我answer,涵盖了两种情况,即单选和多选。【参考方案5】:我猜你必须从System.Web.Mvc
汇编中反汇编html.LabelFor(LabelExtensions.LabelFor<TModel,TValue>
的代码。
比如看ExpressionHelper.GetExpressionText
至于用属性成员值替换成员名称 - 您必须使用老式反射。
【讨论】:
以上是关于如何使用 lambda 表达式和匿名类型获取类型的属性名称?的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin函数 ⑤ ( 匿名函数变量类型推断 | 匿名函数参数类型自动推断 | 匿名函数又称为 Lambda 表达式 )
KotlinKotlin 函数总结 ( 具名函数 | 匿名函数 | Lambda 表达式 | 闭包 | 内联函数 | 函数引用 )
KotlinKotlin 函数总结 ( 具名函数 | 匿名函数 | Lambda 表达式 | 闭包 | 内联函数 | 函数引用 )