优化IEnumerable扩展MaxBy

本文关键字:MaxBy 扩展 IEnumerable 优化 | 更新日期: 2023-09-27 18:00:23

我有一个IEnumerable扩展MaxBy,可以像一样使用

var longest = new [] {"cat", "dogs", "nit" }.MaxBy(x=>x.Count())

应该是

"dogs"

带有实现*强调文本*

public static T MaxBy<T,M>(this IEnumerable<T> This, Func<T,M> selector)
    where M : IComparable
{
    return This
        .Skip(1)
        .Aggregate
        ( new {t=This.First(), m = selector(This.First())}
        , (a, t) =>
            {
                var m = selector(t);
                if ( m.CompareTo(a.m) > 0)
                {
                    return new { t, m };
                }
                else
                {
                    return a;
                }
            }
        , a => a.t);
}

它相当优雅,纯粹是功能性的,但我发现有问题。我正在使用匿名对象它们是引用类型并且需要垃圾收集。在旅行的最坏情况下长度为N的IEnumerable将进行N次内存分配,N个对象将需要垃圾收集

我可以编写使用外部可变累加器的代码,但从美学角度来说,我更喜欢坚持我的模式。

然而,我的担忧在现实中是个问题吗?.Net一代人垃圾收集器识别出这些对象的寿命很短,只有一个位于一段时间并优化掉正在发生的事情?还是我创建一个自定义值类型(结构)来保存我的累加器,而不是使用匿名对象。

**编辑**

这显然是一种非功能性的方式

public static T MaxBy<T,M>(this IEnumerable<T> This, Func<T,M> selector)
    where M : IComparable
{
    var t = This.First();
    var max = selector(t);
    foreach (var item in This.Skip(1))
    {
        var m = selector(item);
        if ( m.CompareTo(max) > 0)
        {
            max = m;
            t = item;
        }
    }
    return t;
}

优化IEnumerable扩展MaxBy

这个更好:

public static T MaxBy<T, M>(this IEnumerable<T> source, Func<T, M> selector)
  where M : IComparable
{
  return source.Aggregate((record, next) =>
     Comparer<M>.Default.Compare(selector(next), selector(record)) > 0
     ? next
     : record);
}
  • 您没有那么多代码(更智能地使用.Aggregate<>
  • 您避免使用匿名类型
  • 使用Comparer<M>.Default可以避免在非常常见的情况下装箱,在这种情况下,M是一个实际上是IComparable<M>(而不仅仅是IComparble)的值类型。在您的示例中,Mint,这适用!对于值类型和引用类型,如果M真的实现了泛型IComparable<M>,则可以绕过类型检查。但是,如果M是一个只有非泛型IComparable(约束where M : IComparable)的"差"类型,那么一切都会起作用

source为空时,InvalidOperationException的行为保持不变。