Самым изящным с точки зрения того, чтобы сделать это один раз в небольшом количестве кода, является, как уже упоминалось:
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?уже делать то , что мы хотим , чтобы в любом случае (как ответ марки Marc Gravell по использованию). Точно так же мы также получаем желаемое поведение от 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)
{
var result = source.Min();
return result == null ? defaultValue : result;
}
else
{
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…).