Самое лучшее с точки зрения того, чтобы сделать это один раз в небольшом количестве кода, как уже упоминалось:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).DefaultIfEmpty().Min();
С приведением itm.Amount
к decimal?
и получением Min
этого существасамое подходящее, если мы хотим иметь возможность обнаружить это пустое состояние.
Если, однако, вы действительно хотите предоставить MinOrDefault()
, тогда мы, конечно, можем начать с:
public static TSource MinOrDefault<TSource>(this IQueryable<TSource> source, TSource defaultValue)
{
return source.DefaultIfEmpty(defaultValue).Min();
}
public static TSource MinOrDefault<TSource>(this IQueryable<TSource> source)
{
return source.DefaultIfEmpty(defaultValue).Min();
}
public static TResult MinOrDefault<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, TSource defaultValue)
{
return source.DefaultIfEmpty(defaultValue).Min(selector);
}
public static TResult MinOrDefault<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)
{
return source.DefaultIfEmpty().Min(selector);
}
Вы сейчасиметь полный набор MinOrDefault
вне зависимости от того, включаете ли вы селектор и указываете ли вы значение по умолчанию.
С этого момента ваш код просто:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).MinOrDefault();
Итакхотя с самого начала он не такой аккуратный, с тех пор он становится более аккуратным.
Но подождите!Это еще не все!
Допустим, вы используете EF и хотите использовать поддержку async
.Легко сделать:
public static Task<TSource> MinOrDefaultAsync<TSource>(this IQueryable<TSource> source, TSource defaultValue)
{
return source.DefaultIfEmpty(defaultValue).MinAsync();
}
public static Task<TSource> MinOrDefaultAsync<TSource>(this IQueryable<TSource> source)
{
return source.DefaultIfEmpty(defaultValue).MinAsync();
}
public static Task<TSource> MinOrDefaultAsync<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, TSource defaultValue)
{
return source.DefaultIfEmpty(defaultValue).MinAsync(selector);
}
public static Task<TSource> MinOrDefaultAsync<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)
{
return source.DefaultIfEmpty().MinAsync(selector);
}
(Обратите внимание, что я не использую здесь await
; мы можем напрямую создать Task<TSource>
, который делает то, что нам нужно без него, и, следовательно, избежать скрытых осложнений await
приносит).
Но подождите, это еще не все!Допустим, мы используем это с IEnumerable<T>
несколько раз.Наш подход является неоптимальным.Конечно, мы можем добиться большего успеха!
Во-первых, Min
, определенные для int?
, long?
, float?
double?
и decimal?
, уже в любом случае делают то, что мы хотим (как говорит ответ Марка Гравелла)использование).Точно так же мы получаем желаемое поведение из Min
, которое уже определено, если вызывается для любого другого T?
.Итак, давайте сделаем несколько небольших и, следовательно, легко встроенных методов, чтобы воспользоваться этим фактом:
public static TSource? MinOrDefault<TSource>(this IEnumerable<TSource?> source, TSource? defaultValue) where TSource : struct
{
return source.Min() ?? defaultValue;
}
public static TSource? MinOrDefault<TSource>(this IEnumerable<TSource?> source) where TSource : struct
{
return source.Min();
}
public static TResult? Min<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult?> selector, TResult? defaultValue) where TResult : struct
{
return source.Min(selector) ?? defaultValue;
}
public static TResult? Min<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult?> selector) where TResult : struct
{
return source.Min(selector);
}
Теперь давайте начнем с более общего случая:
public static TSource MinOrDefault<TSource>(this IEnumerable<TSource> source, TSource defaultValue)
{
if(default(TSource) == null) //Nullable type. Min already copes with empty sequences
{
//Note that the jitter generally removes this code completely when `TSource` is not nullable.
var result = source.Min();
return result == null ? defaultValue : result;
}
else
{
//Note that the jitter generally removes this code completely when `TSource` is nullable.
var comparer = Comparer<TSource>.Default;
using(var en = source.GetEnumerator())
if(en.MoveNext())
{
var currentMin = en.Current;
while(en.MoveNext())
{
var current = en.Current;
if(comparer.Compare(current, currentMin) < 0)
currentMin = current;
}
return currentMin;
}
}
return defaultValue;
}
Теперь очевидноепереопределения, которые используют это:
public static TSource MinOrDefault<TSource>(this IEnumerable<TSource> source)
{
var defaultValue = default(TSource);
return defaultValue == null ? source.Min() : source.MinOrDefault(defaultValue);
}
public static TResult MinOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, TResult defaultValue)
{
return source.Select(selector).MinOrDefault(defaultValue);
}
public static TResult MinOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
return source.Select(selector).MinOrDefault();
}
Если мы действительно настроены на повышение производительности, мы можем оптимизировать для определенных случаев, точно так же, как Enumerable.Min()
делает:
public static int MinOrDefault(this IEnumerable<int> source, int defaultValue)
{
using(var en = source.GetEnumerator())
if(en.MoveNext())
{
var currentMin = en.Current;
while(en.MoveNext())
{
var current = en.Current;
if(current < currentMin)
currentMin = current;
}
return currentMin;
}
return defaultValue;
}
public static int MinOrDefault(this IEnumerable<int> source)
{
return source.MinOrDefault(0);
}
public static int MinOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector, int defaultValue)
{
return source.Select(selector).MinOrDefault(defaultValue);
}
public static int MinOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
return source.Select(selector).MinOrDefault();
}
И такдля long
, float
, double
и decimal
в соответствии с набором Min()
, предоставленным Enumerable
.Это как раз то, где шаблоны T4 полезны.
В конце концов, у нас почти такая же эффективная реализация MinOrDefault()
, как мы могли бы надеяться, для широкого диапазона типов.Конечно, не «аккуратный» перед лицом одного использования (опять же, просто используйте DefaultIfEmpty().Min()
), но очень «аккуратный», если мы часто его используем, поэтому у нас есть хорошая библиотека, которую мы можем использовать повторно (или дажевставьте в ответы в StackOverflow…).