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