【问题标题】:Losing foreach with Linq queries使用 Linq 查询丢失 foreach
【发布时间】:2014-03-14 19:28:19
【问题描述】:

我想知道是否可以以某种方式(如果可能)用 LINQ 查询替换这个 foreach

在线游戏笔:https://ideone.com/PQEytf

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

public class Test
{
    public static void Main()
    {
        // dummy inputs for test sake
        var keys = new[] { "key1", "key2" };

        var services = new Dictionary<string, Service>
        {
            {"key1", new Service {Components = new Dictionary<string, string> {{"comp1", "value1"}}}},
            {"key2", new Service {Components = new Dictionary<string, string> {{"comp2", "value2"}}}}
        };


        var serviceComponents = GetServiceComponents(keys, services);
        // do something with it
    }

    public static IEnumerable<ServiceComponent> GetServiceComponents(string[] keys, Dictionary<string, Service> services)
    {
        var serviceComponents = new List<ServiceComponent>();

        // this is the foreach that I want to lose
        foreach (
            var key in
                keys.Select(
                    name =>
                        services.FirstOrDefault(
                            x => x.Key.Equals(name))))
        {
            serviceComponents.AddRange(key.Value.Components.Select(component => new ServiceComponent
            {
                Name = key.Key,
                Service = component.Key,
                Value = component.Value,
            }));
        }

        return serviceComponents.ToArray();

    }
}

public class Service
{
    public Dictionary<string, string> Components { get; set; }
}

public class ServiceComponent
{
    public string Name { get; set; }
    public string Service { get; set; }
    public string Value { get; set; }
}

【问题讨论】:

    标签: c# linq foreach


    【解决方案1】:

    是的,您要查找的是SelectMany。这允许您将序列中的每个项目转换为另一个序列,然后将所有这些序列展平为一个序列。 (通过将所有序列放入一个列表中,您完成了同样的事情,没有延迟执行。)

    return keys.SelectMany(name => services.FirstOrDefault(x => x.Key.Equals(name))
        .Value.Components
        .Select(component => new ServiceComponent
        {
            Name = name.Key,
            Service = component.Key,
            Value = component.Value,
        }))
        .ToArray();
    

    话虽如此,此查询所做的是获取您的每个键,使用线性搜索在服务中找到相应的项目,然后映射结果。与其使用FirstOrDefault 进行线性搜索,不如使用字典的本机功能有效且高效地查找每个键的值:

    return keys.SelectMany(key => services[key].Components
        .Select(component => new ServiceComponent
        {
            Name = key,
            Service = component.Key,
            Value = component.Value,
        }))
        .ToArray();
    

    【讨论】:

    • 我知道答案将围绕SelectMany 展开,但我无法完全确定。另外,关于失去FirstOrDefault 的第二点是锦上添花!谢谢!
    【解决方案2】:

    为了扩展 @Servy 的示例,我经常发现 LINQ 表达式语法比 lambda SelectMany 更容易阅读(它转换为相同的东西)。这是他使用查询表达式的查询:

    return from key in keys
           from component in services[key].Components
           select new ServiceComponent
           {
              Name = key,
              Service = component.Key,
              Value = component.Value,
           }))
           .ToArray();
    

    【讨论】:

    • 那不叫LINQ语法,那是查询语法。 LINQ 是System.Linq 命名空间中的方法的名称。两种解决方案都使用 LINQ。
    • 它是一种语言集成查询 (LINQ) 语法。这个使用查询表达式而不是 lambda 表达式。
    • MSDN defines it as query syntax,不是 LINQ 语法。 LINQ 语法不是一个东西,至少不是正式的。另一种语法是方法语法,而不是 lambda 语法。这两者都是编写 LINQ 查询的两种不同语法。
    • 完全同意可读性,至少对于 lambdas/LINQ 经验较少的人或在移动设备上查看此答案的人来说是这样! :)
    • 而 C# 规范(第 17.6 节)和 MSDN C# programming guide 将其称为查询表达式。我选择在这里不说 LINQ 查询表达式,因为“语言集成查询查询表达式”有点多余。不要试图进入语义辩论。只是想指出另一种替代语法,不管它叫什么。
    【解决方案3】:

    您需要的是SelectMany,但是,通过使用FirstOrDefault,您将向NullReferenceException 敞开大门(因为任何引用类型的默认值都是null)。如果您打算在keys 的元素不是services 中的键时抛出NullReferenceException,那么您可以使用利用SelectMany 的其他答案。但是,如果您不打算使用NullReferenceException,那么您应该使用以下内容:

    return services.Where(pair => keys.Contains(pair.Key))
                   .SelectMany(pair => 
                               pair.Value.Components
                                    .Select(component => new ServiceComponent
                                                             {
                                                                 Name = pair.Key,
                                                                 Service = component.Key,
                                                                 Value = component.Value
                                                              }))
                   .ToArray();
    

    此语句从services 中删除所有键值对,其键不在keys 数组中,然后将每个对的Components 的每个元素转换为新的ServiceComponent 对象,SelectMany 使新的ServiceComponents 中的单个平面列表。

    【讨论】:

    • 我喜欢这个解决方案的弹性!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多