1

我有一个 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);
}

它非常优雅且纯粹是功能性的,但我看到了一个问题。我正在使用作为引用类型并需要垃圾收集的匿名对象。在最坏的情况下,当遍历长度为 NI 的 IEnumerable 时,将进行 N 个内存分配,并且 N 个对象将需要垃圾回收。

我可以编写代码以使用外部可变累加器,但从美学上讲,我更愿意坚持我拥有的模式。

然而,我的担忧在现实中有问题吗?.Net 分代垃圾收集器是否识别出这些对象的寿命很短,一次只有一个并优化正在发生的事情?或者我创建一个自定义值类型( struct )来保存我的累加器而不是使用匿名对象更好。

** 编辑 **

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

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;
}
4

1 回答 1

2

这个更好:

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)。在您的示例 where Mwas 中int,这适用!对于值类型和引用类型,如果M真的实现了泛型,您可以绕过类型检查IComparable<M>M但是,如果是只有非泛型IComparable(约束)的“差”类型,一切仍然有效where M : IComparable

为空InvalidOperationException时的行为不变。source

于 2018-05-07T14:42:41.153 回答