Điều gọn gàng nhất về việc chỉ thực hiện một lần với một mã số tiền nhỏ, như đã đề cập:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).DefaultIfEmpty().Min();
Với việc truyền itm.Amount
đến decimal?
và lấy được Min
điều đó là gọn gàng nhất nếu chúng ta muốn có thể phát hiện điều kiện trống này.
Tuy nhiên, nếu bạn thực sự muốn cung cấp MinOrDefault()
thì tất nhiên chúng ta có thể bắt đầu với:
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);
}
Bây giờ bạn có một tập hợp đầy đủ về MinOrDefault
việc bạn có bao gồm bộ chọn hay không và bạn có chỉ định mặc định hay không.
Từ thời điểm này, mã của bạn chỉ đơn giản là:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).MinOrDefault();
Vì vậy, mặc dù nó không gọn gàng như ban đầu, nhưng từ đó nó sẽ gọn gàng hơn.
Nhưng đợi đã! Còn nữa!
Giả sử bạn sử dụng EF và muốn sử dụng async
hỗ trợ. Dễ dàng thực hiện:
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);
}
(Lưu ý rằng tôi không sử dụng await
ở đây; chúng ta có thể trực tiếp tạo ra một Task<TSource>
cái mà chúng ta cần mà không cần nó, và do đó tránh được những phức tạp tiềm ẩn await
mang lại).
Nhưng xin chờ chút nữa! Giả sử chúng tôi đang sử dụng điều này với IEnumerable<T>
một số lần. Cách tiếp cận của chúng tôi là không tối ưu. Chắc chắn chúng tôi có thể làm tốt hơn!
Thứ nhất, Min
xác định trên int?
, long?
, float?
double?
và decimal?
đã làm những gì chúng muốn anyway (như làm cho câu trả lời Marc Gravell của việc sử dụng). Tương tự, chúng tôi cũng nhận được hành vi chúng tôi muốn từ hành vi Min
đã được xác định nếu được gọi cho bất kỳ hành vi nào khác T?
. Vì vậy, chúng ta hãy thực hiện một số phương pháp nhỏ, và do đó dễ dàng nội dung, để tận dụng thực tế này:
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);
}
Bây giờ hãy bắt đầu với trường hợp tổng quát hơn trước:
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;
}
Bây giờ các ghi đè rõ ràng sử dụng điều này:
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();
}
Nếu chúng tôi thực sự lạc quan về hiệu suất, chúng tôi có thể tối ưu hóa cho một số trường hợp nhất định, giống như 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();
}
Và vân vân cho long
, float
, double
và decimal
để phù hợp với bộ Min()
cung cấp bởi Enumerable
. Đây là loại mà các mẫu T4 hữu ích.
Cuối cùng, chúng tôi có một triển khai hiệu quả MinOrDefault()
như chúng tôi có thể hy vọng, cho một loạt các loại. Chắc chắn là không "gọn gàng" khi sử dụng một lần cho nó (một lần nữa, chỉ sử dụng DefaultIfEmpty().Min()
), nhưng rất "gọn gàng" nếu chúng ta thấy mình sử dụng nó nhiều, vì vậy chúng ta có một thư viện đẹp, chúng ta có thể sử dụng lại (hoặc thực sự, dán vào câu trả lời trên StackOverflow…).