【问题标题】:Can this be refactored into nicey nice LINQ?这可以重构为漂亮的 LINQ 吗?
【发布时间】:2008-10-25 16:49:37
【问题描述】:

我有一个 Breadcrumb 类型的 IList,它只是一个具有 NavigationTitle、NavigationUrl 和 IsCurrent 属性的轻量级类。它缓存在网络服务器上。我有一个方法可以构建当前的面包屑跟踪,直到第一个将 IsCurrent 设置为 true 的面包屑......使用下面的代码。它非常丑陋,绝对是一个快速的垃圾袋威利解决方案,但我很好奇,这可以很容易地重构为 LINQ 吗?

IList<Breadcrumb> crumbs = new List<Breadcrumb>();
bool foundCurrent = false;
for (int a = 0; a < cachedCrumbs.Count; a++)
{
    crumbs.Add(crumbs[a]);
    if (foundCurrent)
    {
      break;
    }
    foundCurrent = (crumbs[a + 1] != null && ((Breadcrumb)crumbs[a + 1]).IsCurrent);
}

【问题讨论】:

    标签: linq refactoring


    【解决方案1】:

    我按我的想法输入这个,以便它显示一个思路以及一个答案。

    • 您的来源只是缓存了Crumbs
    • 您想添加第一个 设置了 IsCurrent 的碎屑,但之后什么都没有
    • TakeWhile 听起来像是要走的路,但获取“以前的值有 IsCurrent”有点痛苦
    • 我们可以使用闭包来有效地保留一个变量,以确定最后一个值是否设置了 IsCurrent
    • 我们可以做一个有点“无操作”的选择,将 TakeWhile 与是否继续运行的工作区分开来

    所以,我们最终得到:

    bool foundCurrent = false;
    
    var crumbs = cachedCrumbs.TakeWhile(crumb => !foundCurrent)
                             .Select(crumb => { 
                                     foundCurrent = crumb == null || !crumb.IsCurrent; 
                                     return crumb; });
    

    我还没有尝试过,但我认为它应该可以工作...不过可能有更简单的方法。

    编辑:我认为在这种情况下,实际上一个直接的 foreach 循环 更简单。话虽如此,您可以编写另一个类似于 TakeWhile 的扩展方法,除了它返回导致条件失败的元素。那么它会很简单:

    var crumbs = cachedCrumbs.NewMethod(crumb => crumb == null || !crumb.IsCurrent);
    

    (目前我想不出一个像样的方法名称,因此NewMethod!)

    【讨论】:

    • 嗨乔恩,这工作!谢谢你。我了解选择在做什么,但我对 .TakeWhile(crumb => foundCurrent) 有点迷失 - 那到底是在做什么?而且,为什么我们会在选择中再次看到 foundCurrent?
    • TakeWhile 实际上是错误的——它应该是“!foundCurrent”而不是“foundCurrent”——我已经修复了它。 TakeWhile lambda 只是“在我们还没有找到当前的时候继续前进”。 select lambda 基本上是设置 foundCurrent after TakeWhile.
    【解决方案2】:

    首先,该代码不起作用。我猜你使用“crumbs”的一些地方是指“cachedCrumbs”。如果是这样,代码可以简化为:

    IList<Breadcrumb> crumbs = new List<Breadcrumb>();
    for (int a = 0; a < cachedCrumbs.Count; a++)
    {
        crumbs.Add(cachedCrumbs[a]);
        if (cachedCrumbs[a] != null && cachedCrumbs[a].IsCurrent)
        {
              break;
        }
    }
    

    【讨论】:

    • 嗨詹姆斯。代码确实有效,我只是试图给出一个高层次的概述。我不是指我缓存了Crumbs 的面包屑。 cachedCrumbs 是所有可能的面包屑,按顺序排列。将其视为工作流程中的步骤,而不是实际的网络面包屑。感谢您提供有关删除 foundCurrent 的提示!
    • 不,你误读了 James 的评论 - 你的代码有:“crumbs.Add(crumbs[a]);”你实际上的意思是“crumbs.Add(cachedCrumbs[a]);”
    【解决方案3】:

    基于 James Curran 的替代答案 - 这当然可以使用 foreach 语句来改进:

    IList<Breadcrumb> crumbs = new List<BreadCrumb>();
    foreach (Breadcrumb crumb in cachedCrumbs)
    {
        crumbs.Add(crumb);
        if (crumb != null && crumb.IsCurrent)
        {
            break;
        }
    }
    

    【讨论】:

    • foreach 循环不保证面包屑按照它们实际出现的顺序被访问。
    • 它们适用于所有支持按数字索引的合理集合。你能想到一个在这方面行为不端的常见 IList 实现吗?
    • 感谢 foreach .. 我先有那个,但由于某种原因把它拿出来了。 :) 这是之后留下的烂摊子。 :)
    • 我把它拿出来是因为出于某种原因,我认为我必须检查下一个碎屑以确定 IsCurrent == 是否为真。这就是为什么我删除了foreach。事实证明,不再需要了,所以我将回到 foreach 或上面的 LINQ 建议。
    • 啊..我想不出...我的意思是文档中没有保证,仅此而已。
    【解决方案4】:

    怎么样...

    // find the current item
    var currentItem = cachedCrumbs.First(c => c.IsCurrent);
    var currentIdx = cachedCrumbs.IndexOf(currentItem);
    
    // get all items upto current item
    var crumbs = cachedCrumbs.Take(currentIdx + 2);
    

    您可以将其转换为 TakeUpto 方法,该方法将所有项目带到与您提供的谓词匹配的项目。

    怎么样:

    public static IEnumerable<T> TakeUpto<T>(this IList<T> theList, Func<T, bool> predicate)
    {
        var targetItem = theList.First(predicate);
        var targetIdx = theList.IndexOf(targetItem);
    
        return theList.Take(targetIdx + 2);
    }
    

    那么你可以这样使用它:

    var crumbs = cachedCrumbs.TakeUpto(c => c.IsCurrent);
    

    干净多了!

    没有检查空值和非一案例以及 IList/IEnumerable 差异,但您应该明白了。

    【讨论】:

    • Chakrit,我非常喜欢这个解决方案并决定使用它。谢谢你。 :) 我喜欢它的重复使用候选资格。
    • Jon 的解决方案可以重构为可重用的扩展方法……但它有点复杂……我更喜欢可读性 :-) 但 Jon 的解决方案可能执行得更快
    • 请注意,此解决方案依赖于列表枚举数的排序,就像您批评的 foreach 一样 :)
    • (是的,我认为它差了一个 - 如果它与第一个元素匹配,IndexOf 将返回 0,但你想取一个元素。)我对这个解决方案的主要关注是它会迭代在这个序列上 3 次。在一些情况下会很昂贵。
    • 只是想一想——因为它只能在 ILists 上工作,所以它比处理 IEnumerable 成本更低。 ILists 不会被懒惰地评估,而是完全在内存中。诚然,拥有一个懒惰的 IList 并非不可能……只是不太可能。
    【解决方案5】:

    这个答案是 chakrit 的 TakeUpTo 的替代实现:

    public static IEnumerable<T> TakeUpto<T>(this IEnumerable<T> theList, Func<T, bool> predicate)
    {
        foreach (T element in theList)
        {
            yield return element;
            if (predicate(element))
            {
                break;
            }
        }
    }
    

    这只会遍历列表一次,这在各种情况下都可能相关。 (假设上游序列是 OrderBy 子句的结果 - 你真的不希望它无缘无故地对结果进行多次排序。)

    它还允许任何IEnumerable&lt;T&gt;作为源,这使得它更加灵活。

    LINQ 的一大优点是实现同一目标的多种方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-30
      • 2013-01-23
      相关资源
      最近更新 更多