이미 언급했듯이 소량의 코드로 한 번만 수행하는 가장 좋은 점은 다음과 같습니다.
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?
이미 (마크 Gravell의 응답 차종이의 사용으로) 우리는 어쨌든 원하는 일을. 마찬가지로, Min
다른 .NET Framework를 호출 하면 이미 정의 된 에서 원하는 동작을 얻습니다 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에 대한 답변…).