Найкращий з точки зору простого виконання одного разу в невеликій кількості коду, як уже зазначалося:
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 ...).