linq 식에서 10 진수 값 목록을 생성하고 있으며 0이 아닌 최소값을 원합니다. 그러나 linq 표현식이 빈 목록을 생성하는 것은 전적으로 가능합니다.
이 경우 예외가 발생하며이 상황에 대처할 수있는 MinOrDefault가 없습니다.
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).Min();
목록이 비어있는 경우 결과를 0으로 설정하는 가장 좋은 방법은 무엇입니까?
답변
decimal? result = (from Item itm in itemList
where itm.Amount != 0
select (decimal?)itm.Amount).Min();
로 변환합니다 decimal?
. 아무것도 없으면 빈 결과를 얻습니다 (사실 후에 처리하십시오-주로 예외를 중지하는 방법을 설명하고 있습니다). 또한 “비 제로”사용했다 !=
보다는 >
.
답변
원하는 것은 다음과 같습니다.
IEnumerable<double> results = ... your query ...
double result = results.MinOrDefault();
글쎄, MinOrDefault()
존재하지 않습니다. 그러나 우리가 직접 구현한다면 다음과 같이 보일 것입니다.
public static class EnumerableExtensions
{
public static T MinOrDefault<T>(this IEnumerable<T> sequence)
{
if (sequence.Any())
{
return sequence.Min();
}
else
{
return default(T);
}
}
}
그러나 System.Linq
동일한 결과를 생성하는 기능이 있습니다 (약간 다른 방식으로).
double result = results.DefaultIfEmpty().Min();
경우 results
시퀀스에 요소가없는, DefaultIfEmpty()
더 – 하나 개의 요소를 포함하는 시퀀스를 생성합니다 default(T)
– 이후에 호출 할 수 있습니다 Min()
에 있습니다.
default(T)
원하는 것이 아닌 경우 다음을 사용하여 고유 한 기본값을 지정할 수 있습니다.
double myDefault = ...
double result = results.DefaultIfEmpty(myDefault).Min();
이제 깔끔합니다!
답변
이미 언급했듯이 소량의 코드로 한 번만 수행하는 가장 좋은 점은 다음과 같습니다.
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) //Nullable type. Min already copes with empty sequences
{
//Note that the jitter generally removes this code completely when `TSource` is not nullable.
var result = source.Min();
return result == null ? defaultValue : result;
}
else
{
//Note that the jitter generally removes this code completely when `TSource` is nullable.
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에 대한 답변…).
답변
이 접근 방식은 Amount
에서 가장 작은 값 하나를 반환합니다 itemList
. 이론적으로이 해야 데이터베이스에 여러 개의 라운드 트립을 피할 수 있습니다.
decimal? result = (from Item itm in itemList
where itm.Amount > 0)
.Min(itm => (decimal?)itm.Amount);
nullable 형식을 사용하고 있기 때문에 null 참조 예외가 더 이상 발생하지 않습니다.
를 Any
호출하기 전에 와 같은 실행 메소드의 사용을 피함으로써 Min
데이터베이스로 한 번만 이동해야합니다.
답변
itemList가 nullable이 아니고 (DefaultIfEmpty가 0을 제공하는 경우) 잠재적 출력 값으로 null을 원하는 경우 람다 구문도 사용할 수 있습니다.
decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);