通过集合过滤IEnumerable(Linq)

4
我希望您能够通过IEnumerable对象中的特定属性对其进行过滤。我希望可以选择通过一个或多个属性值进行过滤,但是想要过滤多少个值(以及哪些值)只有在运行时才知道。
好的,为了举例说明,收集到的对象可能是以下结构体:
public struct Person
{
    public string Name { get; set; }
    public string Profession{ get; set; }
}

下面是我填充了一些随机值的列表,可以使用这个结构体:

List<Person> people= new List<Person>;
people.Add(new Person(){Name = "Mickey", Profession="Tinker"};
people.Add(new Person(){Name = "Donald", Profession="Tailor"};
people.Add(new Person(){Name = "Goofy", Profession="Soldier"};
people.Add(new Person(){Name = "Pluto", Profession="Spy"};

这些内容会先被转移到一个 IEnumerable 中。

var wantedPeople = from n in this.people select n;

假设一个用户只对"裁缝"和"间谍"这两个职业感兴趣,并通过某种GUI技巧创建了以下集合:

List<string> wantedProfessions = new List<string>();
wantedProfessions.Add("Tailor");
wantedProfessions.Add("Spy");

现在我想使用Linq语句来过滤我的wantedPeople,使其只包括裁缝和间谍的条目。我知道我可以使用where子句,但我不知道如何调整它以获得我想要的结果(并且以下操作不是我想要的,因为它仅适用于上面的wantedProfessions集合(例如,此集合将在运行时更改):)。
wantedPeople = from n in wantedPeople
              where n.Profession == wantedProffessions[0] || n.Profession == wantedProffessions[1]
              select n;
2个回答

7

如果您想从给定的列表中检查任何想要的职业:

wantedPeople = from n in wantedPeople
               where wantedProffessions.Contains(n.Profession)
               select n;

或者你可以通过逐个应用筛选器来使用Lambda语法构建查询:
var query = people.AsEnumerable();

if (!String.IsNullOrEmpty(name))
    query = query.Where(p => p.Name == name);

if (wantedProfessions.Any())
    query = query.Where(p => wantedProfessions.Contains(p.Profession));

如果你想创建更复杂的过滤器,例如某个名称和多个职业,你可以使用规约模式(Specification pattern)。规约可以通过这个简单的接口进行定义:
public interface ISpecification<T>
{
   bool Satisfied(T entity);
}

它只是检查给定的实体(人)是否符合规格。规格看起来也非常简单:

public class PersonNameSpecification : ISpecification<Person>
{
    private string _name;
    public PersonNameSpecification(string name)
    {
        _name = name;
    }

    public bool Satisfied(Person person)
    {
        return person.Name == _name;
    }
}

职业说明:

public class PersonProfessionSpecification : ISpecification<Person>
{
    private string[] _professions;
    public PersonProfessionSpecification(params string[] professions)
    {
        _professions = professions;
    }
    public bool Satisfied(Person person)
    {
        return _professions.Contains(person.Profession);
    }
}

您可以创建实现布尔逻辑的规范,例如OrSpecification或AndSpecification:
public class AndSpecification<T> : ISpecification<T>
{
    private ISpecification<T> _specA;
    private ISpecification<T> _specB;
    public AndSpecification(ISpecification<T> specA, ISpecification<T> specB)
    {
        _specA = specA;
        _specB = specB;
    }
    public bool Satisfied(T entity)
    {
        return _specA.Satisfied(entity) && _specB.Satisfied(entity);
    }
}

public static class SpecificationExtensions
{
    public static ISpecification<T> And<T>(
       this ISpecification<T> specA, ISpecification<T> specB)
    {
        return new AndSpecification<T>(specA, specB);
    }
}

现在您可以创建复杂的规格说明,描述您想要获取的人员:
var professionSpec = new PersonProfessionSpecification("Tailor", "Spy");
var nameSpec = new PersonNameSpecification("Pluto");
var spec = professionSpec.And(nameSpec);

获取所需的人员:

var result = people.Where(spec.Satisfied);

1
感谢Sergey,对于长时间的延迟表示歉意。 - greenbeast

0

Sergey B的解决方案是针对您的示例的正确方法。

假设您没有使用具有Contains()方法的集合,您也可以执行以下操作:

var wantedPeople = from n in people
                   from p in wantedProffessions
                   where n.Profession.Equals(p)
                   select n;

实际上,在这种情况下,我会选择使用 join - 它在内部创建了一个查找表,比枚举两个集合 O(n*m) 更有效率。 - Sergey Berezovskiy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接