使用 LINQ 检索多个标量值



我想知道当需要来自多个记录的单个值时,是否有一些聪明的方法可以使用 LINQ 从可枚举项中检索数据。

例如,假设您有一个人有三个不同的电话字段:

public class Person
{
    public Phone HomePhone { get; set; }
    public Phone WorkPhone { get; set; }
    public Phone CellPhone { get; set; }
}

。但电话列表以规范化格式存储:

public enum PhoneType 
{
    Home, Work, Cell
}
public class Phone
{
    public PhoneType Type   { get; set; }
    public string    Number { get; set; }
}
static public IEnumerable<Phone> GetPhoneList()
{
    yield return new Phone { Type = PhoneType.Home, Number = "8005551212" };
    yield return new Phone { Type = PhoneType.Work, Number = "8005551313" };
    yield return new Phone { Type = PhoneType.Cell, Number = "8005551414" };
}

如果需要填充 Person,可以编写一个循环,并一次性获取所需的所有内容:

public static Person GetPerson1()
{
    var result = new Person();
    foreach (var ph in GetPhoneList())
    {
        switch (ph.Type)
        {
            case PhoneType.Home: result.HomePhone = ph; break;
            case PhoneType.Work: result.WorkPhone = ph; break;
            case PhoneType.Cell: result.CellPhone = ph; break;
        }
    }
    return result;
}

但是,如果要使用 LINQ,似乎可能需要三次传递:

public static Person GetPerson2()
{
    return new Person
    {
        HomePhone = GetPhoneList().Single( ph => ph.Type == PhoneType.Home ),
        WorkPhone = GetPhoneList().Single( ph => ph.Type == PhoneType.Work ),
        CellPhone = GetPhoneList().Single( ph => ph.Type == PhoneType.Cell )
    };
}

有没有一种聪明的方法可以使用 LINQ 只需一次枚举即可获得所有内容?

如果你想探索我的代码,这里有一个小提琴的链接。

(我知道我可以使用字典或其他数据结构来解决这个特定问题;这只是一个例子。

通常,您无法在 LINQ 中执行此操作。

如果确实需要,可以创建 Foreach 扩展方法,并执行与GetPerson1方法相同的操作。

public static class Ext
{
    public static void Foreach<T>(this IEnumerable<T> e, Action<T> action)
    {
        foreach (T item in e)
        {
            action(item);
        }
    }
}

然后

    public static Person GetPerson2()
    {
        var p = new Person();
        var pl = GetPhoneList();
        pl.Foreach(ph =>
        {
            switch (ph.Type)
            {
                case PhoneType.Home: p.HomePhone = ph; break;
                case PhoneType.Work: p.WorkPhone = ph; break;
                case PhoneType.Cell: p.CellPhone = ph; break;
            }                
        });
        return p;
    }

但你真的不应该。LINQ 旨在对 IEnumerables(逐项(进行操作,并且 LINQ 函数应该没有副作用,而 foreach 循环和 Foreach 扩展方法只会产生副作用,从而更改 Person 对象的状态。

而且,此外,你需要一种"聪明的方法"这一事实应该表明这不是它应该被使用的方式:)

埃里克·利珀特(Eric Lippert(有一篇很棒的文章,其中有更多细节:https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

如果不能保证来自同一个人的号码按顺序排列,那么你必须枚举列表,直到找到所有号码。在我看来,这似乎不是 LINQ 的良好候选项,LINQ 的目的是使代码更具可读性。你的foreach很好,当找到所有数字时,我会打破循环。

如果你想列举所有人,而不仅仅是一个人,那么Dictionary方法可能是最有效的。 GroupBy内部使用字典,您可以使用GroupBy收集属于一个人的所有数字,然后Aggregate从中Person。假设有一些属性Phone.PersonID,并且还有Person.PersonID,那么你会有这样的东西:

GetPhoneList()
    .GroupBy(x => x.PersonID)
    .Select(x => x.Aggregate(new Person() { PersonID = x.Key },
        (person, phone) =>
        {
            switch (phone.Type)
            {
                case PhoneType.Home: person.HomePhone = phone; break;
                case PhoneType.Work: person.WorkPhone = phone; break;
                case PhoneType.Cell: person.CellPhone = phone; break;
            }
            return person;
        }));

我在这里假设GetPhoneList()返回所有人的所有电话。

最新更新