C# 如何实现MaxOrDefault(x=>;x.SomeInt)LINQ扩展?

C# 如何实现MaxOrDefault(x=>;x.SomeInt)LINQ扩展?,c#,linq,extension-methods,C#,Linq,Extension Methods,在IEnumerable上调用.Max(x=>x.SomeInt)时,如果枚举不包含任何元素,则通常会返回“0”。但是,LINQ的.Max(x=>x.SomeInt)实现崩溃,因为该序列不包含任何元素 因此.MaxOrDefault(x=>x.SomeInt)函数将非常有用 我们不应该简单地调用.Any()然后调用.Max(func),因为这会在Resharper中引起合法的“可能的多重枚举”警告 我已实施了以下一项措施: public static TResult MaxOrDefau

在IEnumerable上调用.Max(x=>x.SomeInt)时,如果枚举不包含任何元素,则通常会返回“0”。但是,LINQ的.Max(x=>x.SomeInt)实现崩溃,因为该序列不包含任何元素

因此.MaxOrDefault(x=>x.SomeInt)函数将非常有用

我们不应该简单地调用.Any()然后调用.Max(func),因为这会在Resharper中引起合法的“可能的多重枚举”警告

我已实施了以下一项措施:

    public static TResult MaxOrDefault<T, TResult>(this IEnumerable<T> enumerable, Func<T, TResult> func)
    {
        var list = enumerable.ToList();
        if (!list.Any()) return default(TResult);

        return list.Max(func);
    }
public static TResult MaxOrDefault(此IEnumerable可枚举,Func Func)
{
var list=enumerable.ToList();
如果(!list.Any())返回默认值(TResult);
返回列表.Max(func);
}
然而,这样做的缺点是必须先枚举到列表中,这是次优的,应该是不必要的


有更好的方法吗?< /P> < P>这是我认为更好的/更具体的实现:

public static TResult MaxOrDefault<T, TResult>(this IEnumerable<T> enumerable, Func<T, TResult> func)
{
    return enumerable.Select(func).DefaultIfEmpty().Max();
}
public static TResult MaxOrDefault(此IEnumerable可枚举,Func Func)
{
返回可枚举的.Select(func.DefaultIfEmpty().Max();
}
确保如果没有元素,则返回一个包含单个默认值的
IEnumerable
,该值为
default(TResult)
,即数值类型为0。

如果使用扩展名,则始终保证至少有一个项目(默认项目)的序列如果序列为空

var enumeration = ...;

var max = enumeration.DefaultIfEmpty().Max(x => ...);

您可以利用这样一个事实,即
MoveNext()
在第一次调用时返回false意味着我们肯定处于默认情况,只需遵循该路径,而我们也可以为下一次迭代设置自己:

public static TResult MaxOrDefault<T, TResult>(this IEnumerable<T> source, Func<T, TResult> func) where TResult : IComparable
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
    if(en.MoveNext())
    {
      TResult max = func(en.Current);
      while(en.MoveNext())
      {
        TResult cur = func(en.Current);
        if(max == null || (cur != null && cur.CompareTo(max) > 0))
          max = cur;
      }
      return max;
    }
  else
    return default(TResult);
}
对于某些Linq源,这将更好地工作,例如,将其转换为适当的SQL

但是对于内存中的枚举,它的效率比我们的早期版本低,但是提供这两种方法通常可以让普通重载选择更好的方法。

如果您查看一下,您可以看到它几乎已经达到了您想要的效果,您所需要做的就是更改最后一行代码以返回默认值,而不是抛出错误

    public static TSource MaxOrDefault<TSource>(this IEnumerable<TSource> source) {
        if (source == null) throw Error.ArgumentNull("source");
        Comparer<TSource> comparer = Comparer<TSource>.Default;
        TSource value = default(TSource);
        if (value == null) {
            foreach (TSource x in source) {
                if (x != null && (value == null || comparer.Compare(x, value) > 0))
                    value = x;
            }
            return value;
        }
        else {
            bool hasValue = false;
            foreach (TSource x in source) {
                if (hasValue) {
                    if (comparer.Compare(x, value) > 0)
                        value = x;
                }
                else {
                    value = x;
                    hasValue = true;
                }
            }

            return value;
        }
    }


您可以看看Mono对LINQ方法的实现,Max方法从第1320行开始,因为您可以看到它们只是在collectionInteresting中循环。手动实现它是一个选项。Luka,如果T是正在计算的数字类型,这是一个很好的解决方案。但是,如果T是其他值,例如.MaxOrDefault(x=>x.SomeInt),则这不起作用。这不是很清楚,所以我会编辑这个问题。@布兰登希尔认为<代码> FUNC> /代码>是一个从<代码> t>代码>到<代码> TResult < /代码>的映射,< <代码> DefaultIfEmpty <代码>被调用在<代码> iQueLabe<代码>中,这应该是一个int,考虑到您的函数<代码> x= > x.Apple INT/<代码>返回int。我越喜欢你的。除此之外,使用
IQueryable
IEnumerable
以及使用
IQueryable
定义的一个方法,它可以达到最佳效果。除了当枚举是某个类时,x in.Max(x=>…)将为空外,几乎可以正常工作。卢卡唑有一种类似但固定的溶液。
    public static TSource MaxOrDefault<TSource>(this IEnumerable<TSource> source) {
        if (source == null) throw Error.ArgumentNull("source");
        Comparer<TSource> comparer = Comparer<TSource>.Default;
        TSource value = default(TSource);
        if (value == null) {
            foreach (TSource x in source) {
                if (x != null && (value == null || comparer.Compare(x, value) > 0))
                    value = x;
            }
            return value;
        }
        else {
            bool hasValue = false;
            foreach (TSource x in source) {
                if (hasValue) {
                    if (comparer.Compare(x, value) > 0)
                        value = x;
                }
                else {
                    value = x;
                    hasValue = true;
                }
            }

            return value;
        }
    }
    public static TResult MaxOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) {
        return MaxOrDefault(Enumerable.Select(source, selector));
    }
if (hasValue) return value;
throw Error.NoElements();
return value;