【问题标题】:Something better than .ToArray() to force enumeration of LINQ output比 .ToArray() 更好的东西来强制枚举 LINQ 输出
【发布时间】:2023-03-02 22:23:02
【问题描述】:

我正在使用 LINQ to 对象并有一个函数,在某些情况下,我需要在调用 Aggregate(...) 之前修改基础集合,然后在函数返回 Aggregate(...) 的结果之前将其返回到其原始状态。我当前的代码如下所示:

bool collectionModified = false;
if(collectionNeedsModification)
{
    modifyCollection();
    collectionModified = true;
}

var aggregationResult = from a in
                            (from b in collection
                             where b.SatisfysCondition)
                             .Aggregate(aggregationFunction)
                        select a.NeededValue;

if(collectionModified)
    modifyCollection();

return aggregationResult;

但是,正如所写,如果我修改集合,我会得到错误的结果,因为我在枚举 aggregationResult 之前将集合恢复到其原始状态,并且对 LINQ 结果进行延迟评估。我目前的解决方案是在我的 LINQ 查询中使用 .ToArray(),如下所示:

var aggregationResult = (from a in
                            (from b in collection
                             where b.SatisfysCondition)
                             .Aggregate(aggregationFunction)
                         select a.NeededValue).ToArray();

结果数组的大小总是很小(

【问题讨论】:

    标签: c# linq .net-3.5


    【解决方案1】:

    如果你总是使用结果,我认为你的方法没有问题(因为你的结果集不大,它不会消耗太多内存。顺便说一下,如果你这样做并且从不使用结果,它会造成性能损失)。 所以,是的,这是正确的做法。

    【讨论】:

      【解决方案2】:

      最好避免像上面的 modifyCollection 这样的副作用函数。

      更好的方法是创建一个返回修改后的集合(或查询)的函数,让最初的集合保持原样。

      var modifiedCollection = ModifyCollection(collection, collectionNeedsModification);
      
      var aggregationResult = from a in
                              (from b in modifiedCollection
                               where b.SatisfysCondition)
                               .Aggregate(aggregationFunction)
                          select a.NeededValue;
      

      其中 ModifyCollection 是根据 collectionNeedsModification 布尔参数在参数中返回修改后的集合(或查询)的方法。

      【讨论】:

        【解决方案3】:

        只是为了检查我理解你的意思 - 你基本上想遍历所有结果,只是为了强制任何副作用发生?

        副作用通常是一个坏主意,正是因为这种逻辑更难理解。话虽如此,执行此操作并强制进行全面评估的最简单方法可能就是迭代它:

        foreach (var result in aggregationResult)
        {
            // Deliberately empty; simply forcing evaluation of the sequence.
        }
        

        您也可以使用LastOrDefault() 来避免ToArray() 中涉及的所有复制。只要结果不实现IList<T>(涉及快捷方式),Count() 就可以了。

        【讨论】:

        • 我更喜欢.LastOrDefault()。请注意,LastOrDefault 也有一条捷径(在我的版本中,.NET 4.5.2)。如果源是IList<>,则先获取Count,然后使用索引器获取索引为Count - 1的元素。例如这个工作(使用起订量):var listMock = new Mock<IList<string>>(MockBehavior.Strict); listMock.Setup(x => x.Count).Returns(666); listMock.Setup(x => x[665]).Returns("last one"); var last = listMock.Object.LastOrDefault();
        • 有时,在lazystrict evaluation 之间切换很有用,即使没有涉及其他副作用。这在 Haskell 中尤其明显,其中惰性求值是默认模式。在需要值本身(例如关闭生成值的资源)之前,有时需要或更可取严格评估,并且决定何时强制一个值不应仅限于 a) 创建时和 b) 时需要。
        【解决方案4】:

        (注意:在没有编译器的情况下打字,因此代码未经测试)

        如果您已经将 Reactive Extensions for .NET 作为依赖项,则可以使用 Run():

        aggregationResult.Run();
        

        但可能不值得为此添加依赖项。

        您也可以自己实现 Run 方法作为扩展方法:

        public static MyLinqExtensions 
        {
             public static void Run<T>(this IEnumerable<T> e)
             {
                 foreach (var _ in e);
             }
        }
        

        【讨论】:

        • 当枚举数可能是 alive(如在 RX 中)或作为问题的答案时,这可能会很好。但当我们需要确保收集不懒惰时,不适合一般使用,我认为LastOrDefault - 走捷径 - 更好。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-08
        • 1970-01-01
        • 1970-01-01
        • 2015-02-09
        相关资源
        最近更新 更多