【问题标题】:convert given foreach statements into a single LINQ query将给定的 foreach 语句转换为单个 LINQ 查询
【发布时间】:2019-11-12 17:31:05
【问题描述】:

如何将给定的 foreach 语句转换为单个 LINQ 查询?

foreach (SubsystemBuild img in contentsXMLObj.SubsystemBuilds)
{
    foreach (BuildImage buildImage in build.Images)
    {
        if (buildImage.Name.Equals(img.BuildName) && 
            buildImage.IsOverride == false && 
            img.BaseBuildPath != null)
        {
            buildImage.LocalPath = img.BaseBuildPath;
        }
    }
}

【问题讨论】:

  • 欢迎来到 StackOverflow!由于我们不是代码编写服务,如果您向我们展示您的尝试是如何失败的,您可能会获得更多帮助。
  • 在处理嵌套的foreach 时,您需要在转换为 Linq 时使用 SelectMany。但是,由于这有副作用,您实际上应该仍然至少有一个foreach。事实上,我会说保持原样。
  • @juharr 无法使用 linq 完全替换嵌套的 foreach
  • 是的,您可以替换嵌套的foreach,但是您的代码有副作用,因此您仍然必须遍历 Linq 查询以执行副作用(设置 LocalPath)。我个人认为这段代码不会从转换到 Linq 中受益。
  • 没有理由将此代码转换为 LINQ 格式。我认为它更易读(也更容易维护)。

标签: c# linq foreach


【解决方案1】:

您不会将其作为单个 linq 语句。由于您要为其中一个序列分配更改,因此您仍然需要一个 foreach 循环。但是您可以使用 linq 重新组织代码。

// items in joined are Tuple<BuildImage, string>
var joined = build.Images
     .Where(b => !b.IsOverride)
     .Join( contentsXMLObj.SubsystemBuilds.Where(img => img.BaseBuildPath != null), 
            b => b.Name,
            img => img.BuildName,
            (b, img) => (b, img.BaseBuildPath) );

foreach(var item in joined)
{
   item.Item1.LocalPath = item.Item2;
}

请注意,我仍然更喜欢传统的 vs 查询理解语法,但这并不意味着这不是 linq。

你可以用SelectMany() 做类似的事情,但是那个版本和这个版本对我来说都不是更清晰,我怀疑它们会运行得更快。 我可能会做的仍然是使用嵌套循环,但使用 linq .Where() 操作来处理每个级别的 null 过滤器:

foreach (SubsystemBuild img in contentsXMLObj.SubsystemBuilds.Where(img => img.BaseBuildPath != null))
{
    foreach (BuildImage buildImage in build.Images.Where(b => !b.IsOverride) )
    {
        if (buildImage.Name.Equals(img.BuildName)
        {
            buildImage.LocalPath = img.BaseBuildPath;
        }
    }
}

这更容易理解,并且有机会通过更积极地剔除不合格的项目来提高性能。

在开始时将内部过滤器推入它自己的列表可能会做得更好,以避免重新运行该过滤器。这个想法是用更多的内存来换取更快的整体执行速度:

var images = build.Images.Where(b => !b.IsOverride).ToList();
foreach (var img in contentsXMLObj.SubsystemBuilds.Where(img => img.BaseBuildPath != null))
{
    foreach (var buildImage in images.Where(b => b.Name.Equals(img.BuildName))
    {
        buildImage.LocalPath = img.BaseBuildPath;
    }
}

要考虑的另一件事是,这可能会在同一个对象上设置相同的字段数次。根据每个序列的大小以及它们之间的逻辑关系,您可能通过反转它们很多做得更好。这也让我们可以编写代码,确保我们不会多次尝试分配给同一个对象:

var subbuilds = contentsXMLObj.SubsystemBuilds.Where(img => img.BaseBuildPath is object).ToList();
foreach(var buildImage in build.Images.Where(b =>!b.IsOverride))
{
    var newPath = subbuilds.First(img => buildImage.Name.Equals(img.BuildName))?.BuildPath;
    buildImage.LocalPath = newPath ?? buildImage.LocalPath;
}

现在我喜欢这个!它要短得多,而且仍然很容易理解。而且它可能也表现得更好。

但一如既往,不要将性能假设视为理所当然。那里很容易出错。如果性能很重要,您需要衡量

【讨论】:

  • 您的意思是FirstOrDefault 而不是First
  • 我用LastOrDefault来处理避免多次分配。
  • FirstOrDefault() 可能会更好。 LastOrDefault() 仍然会遍历所有结果,即使您只需要一个结果,而 First 选项可以在第一次匹配后停止循环。
【解决方案2】:

这就是我想要的……

var subsysBuild = contentsXMLObj.SubsystemBuilds
                                .Where(s => s.BuildName.Equals(BuildImage.Name) && s.BaseBuildPath != null && !BuildImage.IsOverride)
                                .FirstOrDefault();
                    BuildImage.LocalPath = subsysBuild != null ? subsysBuild.BaseBuildPath : BuildImage.LocalPath; 
                    });

【讨论】:

    【解决方案3】:

    这是我的努力。

    迭代 1

    //foreach (SubsystemBuild img in contentsXMLObj.SubsystemBuilds)
    //{
    foreach (BuildImage buildImage in build.Images)
    {
        if( buildImage.IsOverride == false && contentsXMLObj.SubsystemBuilds.LastOrDefault( img => buildImage.Name.Equals(img.BuildName) && img.BaseBuildPath != null) is var img && img != null) 
        {
            buildImage.LocalPath = img.BaseBuildPath;
        }
    }
    //}
    

    迭代 2a

    build.Images. 可能需要ToList(),如果它不是List

    //foreach (SubsystemBuild img in contentsXMLObj.SubsystemBuilds)
    //{
    //  foreach (BuildImage buildImage in build.Images)
    //  {
        build.Images.ForEach( buildImage => { 
            if( buildImage.IsOverride == false && contentsXMLObj.SubsystemBuilds.LastOrDefault( img => buildImage.Name.Equals(img.BuildName) && img.BaseBuildPath != null) is var img && img != null) 
            {
                buildImage.LocalPath = img.BaseBuildPath;
            }});
    //  }
    //}
    

    迭代 2b

    build.Images. 可能需要ToList(),如果它不是List

    build.Images.ForEach(buildImage => 
        buildImage.LocalPath = buildImage.IsOverride == false && contentsXMLObj.SubsystemBuilds.LastOrDefault( img => buildImage.Name.Equals(img.BuildName) && img.BaseBuildPath != null) is var img && img != null 
            ? img.BaseBuildPath
            : buildImage.LocalPath);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-17
      • 1970-01-01
      • 2020-12-10
      相关资源
      最近更新 更多