LINQ按名称选择属性[重复]

Posted

技术标签:

【中文标题】LINQ按名称选择属性[重复]【英文标题】:LINQ select property by name [duplicate] 【发布时间】:2017-12-12 21:01:36 【问题描述】:

我正在尝试在 LINQ 选择语句中使用变量。

这是我现在正在做的一个例子。

using System;
using System.Collections.Generic;
using System.Linq;
using Faker;

namespace ConsoleTesting

internal class Program

    private static void Main(string[] args)
    
        List<Person> listOfPersons = new List<Person>
        
            new Person(),
            new Person(),
            new Person(),
            new Person(),
            new Person(),
            new Person(),
            new Person(),
            new Person(),
            new Person(),
            new Person(),
            new Person()
        ;

        var firstNames = Person.GetListOfAFirstNames(listOfPersons);

        foreach (var item in listOfPersons)
        
            Console.WriteLine(item);
        

        Console.WriteLine();
        Console.ReadKey();
    


    public class Person
    
        public string City  get; set; 
        public string CountryName  get; set; 
        public string FirstName  get; set; 
        public string LastName  get; set; 

        public Person()
        
            FirstName = NameFaker.Name();
            LastName = NameFaker.LastName();
            City = LocationFaker.City();
            CountryName = LocationFaker.Country();
        

        public static List<string> GetListOfAFirstNames(IEnumerable<Person> listOfPersons)
        
            return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
        

        public static List<string> GetListOfCities(IEnumerable<Person> listOfPersons)
        
            return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
        

        public static List<string> GetListOfCountries(IEnumerable<Person> listOfPersons)
        
            return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
        

        public static List<string> GetListOfLastNames(IEnumerable<Person> listOfPersons)
        
            return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
        
    


我有一些非常不干燥的代码,带有 GetListOf... 方法

我觉得我应该能够做这样的事情

public static List<string> GetListOfProperty(
IEnumerable<Person> listOfPersons, string property)
        
            return listOfPersons.Select(x =>x.property).Distinct().OrderBy(x=> x).ToList();
        

但这不是有效的代码。我认为关键可能与创建函数有关

如果这就是答案,我该怎么做?

这是使用反射的第二次尝试但这也是不行的。

        public static List<string> GetListOfProperty(IEnumerable<Person> 
listOfPersons, string property)
        
            Person person = new Person();
            Type t = person.GetType();
            PropertyInfo prop = t.GetProperty(property);
            return listOfPersons.Select(prop).Distinct().OrderBy(x => 
x).ToList();

我认为反射可能是一个死胡同/红鲱鱼,但我想我还是会展示我的作品。

注意示例代码实际上已简化,用于通过 AJAX 填充 datalist 以创建自动完成体验。该对象有 20 多个属性,我可以通过编写 20 多个方法来完成,但我觉得应该有一个 DRY 方法来完成它。也使这种方法也可以清理我的控制器动作。

问题:

鉴于第一部分代码,有没有办法将这些类似的方法抽象为一个方法购买将一些对象传递给 select 语句???

感谢您的宝贵时间。

【问题讨论】:

除了显示代码之外,您能否用文字陈述您的问题? DV 调用 GetProperty() 并在这里寻求帮助,甚至没有阅读 MSDN 文档。您的答案在文档中。 @Crowcoder 这只是另一个想要在给定属性名称的情况下选择属性值的人,作为字符串。 @Crowcoder 我添加了空白以更容易地查看问题。 【参考方案1】:

您必须构建选择

.Select(x =>x.property).

手动。幸运的是,这不是一个棘手的问题,因为您希望它始终是同一类型 (string),所以:

var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, property);
var lambda = Expression.Lambda<Func<Person,string>>(body, x);

那么上面的Select就变成了:

.Select(lambda).

(对于基于 IQueryable&lt;T&gt; 的 LINQ)或

.Select(lambda.Compile()).

(对于基于IEnumerable&lt;T&gt; 的LINQ)。

请注意,您可以做任何事情来缓存 property 的最终表单。

【讨论】:

所以我当时的反思是在正确的轨道上? @WizardHammer 可能 - 如果你愿意,你也可以通过反射来做到这一点 - JamesFaix 的回答显示了如何使用 GetValue - 你也可以做类似的事情(我现在不在 IDE ): var func = (Func&lt;Person,string&gt;)Delegate.CreateDelegate(typeof(Func&lt;Person,string&gt;), null, prop.GetGetMethod()); 并将func 传递给Select 谢谢你,Marc 我现在要使用你的代码。我很好奇你和 JamesFaix 答案之间的表现。 @WizardHammer 如果你很少使用它:没关系;如果您经常使用它 - 来自 Delegate.CreateDelegateExpression&lt;T&gt;.Compile 的委托会更快只要您缓存并重用委托实例 - 不要每次都生成它 这段代码我觉得比 JamesFaix 的例子更难阅读。但我能够实现这个添加它的工作原理。【参考方案2】:

从您的示例中,我认为您想要的是:

public static List<string> GetListOfProperty(IEnumerable<Person> 
    listOfPersons, string property)

    Type t = typeof(Person);         
    PropertyInfo prop = t.GetProperty(property);
    return listOfPersons
        .Select(person => (string)prop.GetValue(person))
        .Distinct()
        .OrderBy(x => x)
        .ToList();

typeof 是 C# 中的内置运算符,您可以将类型的名称“传递”给它,它将返回 Type 的相应实例。它在编译时工作,而不是运行时,所以它不像普通函数那样工作。

PropertyInfo 有一个采用对象参数的GetValue 方法。对象是要从中获取属性值的类型实例。如果您尝试定位 static 属性,请使用 null 作为该参数。

GetValue 返回一个object,您必须将其转换为实际类型。

person =&gt; (string)prop.GetValue(person) 是一个带有如下签名的 Lamba 表达式:

string Foo(Person person) ...

如果您希望它适用于任何类型的属性,请将其设为通用而不是硬编码 string

public static List<T> GetListOfProperty<T>(IEnumerable<Person> 
    listOfPersons, string property)

    Type t = typeof(Person);         
    PropertyInfo prop = t.GetProperty(property);
    return listOfPersons
        .Select(person => (T)prop.GetValue(person))
        .Distinct()
        .OrderBy(x => x)
        .ToList();

【讨论】:

我比示例@Marc Gravell 更了解这段代码。在我的情况下,它将始终是一个字符串。【参考方案3】:

我会尽可能远离反射和硬编码字符串...

如何定义一个接受 T 的函数选择器的扩展方法,以便您可以处理字符串属性之外的其他类型

public static List<T> Query<T>(this IEnumerable<Person> instance, Func<Person, T> selector)

    return instance
        .Select(selector)
        .Distinct()
        .OrderBy(x => x)
        .ToList();

假设你有一个 person 类,除了你已经公开的之外,还有一个 int 类型的 id 属性

public class Person

    public int Id  get; set; 
    public string City  get; set; 
    public string CountryName  get; set; 
    public string FirstName  get; set; 
    public string LastName  get; set; 

您需要做的就是使用类型安全的 lambda 选择器获取结果

var ids = listOfPersons.Query(p => p.Id);
var firstNames = listOfPersons.Query(p => p.FirstName);
var lastNames = listOfPersons.Query(p => p.LastName);
var cityNames = listOfPersons.Query(p => p.City);
var countryNames = listOfPersons.Query(p => p.CountryName);

编辑

您似乎确实需要硬编码字符串作为属性输入,那么如何省略一些动态性并使用一些确定性

public static List<string> Query(this IEnumerable<Person> instance, string property)

    switch (property)
    
        case "ids": return instance.Query(p => p.Id.ToString());
        case "firstName": return instance.Query(p => p.FirstName);
        case "lastName": return instance.Query(p => p.LastName);
        case "countryName": return instance.Query(p => p.CountryName);
        case "cityName": return instance.Query(p => p.City);
        default: throw new Exception($"property is not supported");
    

并访问所需的结果

var cityNames = listOfPersons.Query("cityName");

【讨论】:

查看 linqpad 的准备要点以了解整个事情 gist.github.com/dandohotaru/3c2a2b4eb1a07c43cb66c7044ed3f7ce +1 绝对不需要反射或Expression,除非这些名称来自外部源(例如数据库)。使用Func,卢克。 我同意这一点,因为它与我的示例(已简化)有关。但实际上,这是由接收所需列表类型的 Ajax 调用驱动的。并期望返回一个 Json 对象。这是基于@Marc Gravell 示例的内容 public static string GenarateDataList(string property) var requestedProperty = Expression.Parameter(typeof(EquipmentRequest), "x"); var body = Expression.PropertyOrField(requestedProperty, property); var lambda = Expression.Lambda>(body, requestedProperty);使用 (Entities db = new Entities()) var list = db.EquipmentRequests.Select(lambda.Compile()).Distinct().OrderBy(x => x).ToList();返回 JsonConvert.SerializeObject(list); @marcgravell 示例在理论上可以很好地完成工作,但想象一下,在开发过程的后期,您需要将 LastName 重命名为 FamillyName。这意味着您还需要更新客户端,因为客户端会将 LastName 作为属性选择器发送,并且该属性不再存在...【参考方案4】:

你应该可以用反射来做到这一点。我使用它类似的东西。

只要改变你的想法试试这个:

public static List<string> GetListOfValues(IEnumerable<Person> listOfPersons, string propertyName)

    var ret = new List<string>();

    PropertyInfo prop = typeof(Person).GetProperty(propertyName);
    if (prop != null)
        ret = listOfPersons.Select(p => prop.GetValue(p).ToString()).Distinct().OrderBy(x => x).ToList();

    return ret;

希望对你有帮助。

它基于 C# 6

【讨论】:

【参考方案5】:

您也可以使用它。对我有用。

public static class ObjectReflectionExtensions

    public static  object GetValueByName<T>(this T thisObject,  string propertyName)
    
        PropertyInfo prop = typeof(T).GetProperty(propertyName);
        return prop.GetValue(thisObject);

    

然后像这样调用。

public static List<string> GetListOfProperty(IEnumerable<Person> listOfPersons, string propertyName)
    
        return listOfPersons.Select(x =>(string)x.GetValueByName(propertyName)).Distinct().OrderBy(x=> x).ToList();
    

【讨论】:

【参考方案6】:

如果要选择所有值:

object[] foos = objects.Select(o => o.GetType().GetProperty("PropertyName").GetValue(o)).ToArray();

【讨论】:

恕我直言,这个答案没有解决原始问题,虽然我认为好的信息被错误地作为这个问题的答案。

以上是关于LINQ按名称选择属性[重复]的主要内容,如果未能解决你的问题,请参考以下文章

LINQ - 按属性名称选择

在 LINQ 中按特定列分组 [重复]

C# LINQ 组按属性集合,然后按列表定义的显式顺序对每个组进行排序[重复]

按数据名称属性查找 div [重复]

Linq distinct 方法仅适用于特定属性[重复]

JavaScript对象:按名称作为字符串访问变量属性[重复]