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