Linq에서“MinOrDefault”를 달성하는 가장 좋은 방법은 무엇입니까?


82

linq 식에서 10 진수 값 목록을 생성하고 있으며 0이 아닌 최소값을 원합니다. 그러나 linq 표현식이 빈 목록을 생성하는 것은 전적으로 가능합니다.

이 경우 예외가 발생하며이 상황에 대처할 수있는 MinOrDefault가 없습니다.

decimal result = (from Item itm in itemList
                  where itm.Amount > 0
                  select itm.Amount).Min();

목록이 비어있는 경우 결과를 0으로 설정하는 가장 좋은 방법은 무엇입니까?


9
MinOrDefault ()를 라이브러리에 추가 할 것을 제안하는 +1.
J. Andrew Laughlin

답변:


54
decimal? result = (from Item itm in itemList
                  where itm.Amount != 0
                  select (decimal?)itm.Amount).Min();

로 변환합니다 decimal?. 아무것도 없으면 빈 결과를 얻습니다 (사실 후에 처리하십시오-주로 예외를 중지하는 방법을 설명하고 있습니다). 또한 "비 제로"사용했다 !=보다는 >.


흥미 롭군요. 나는 이것이 하늘의리스트를 방지 할 방법을 작동하지 않을 수 있지만 나는 그것을 시도 줄거야
크리스 심슨

7
시도해보십시오 : decimal? result = (new decimal?[0]).Min();제공null
Marc Gravell

2
그리고 아마도 ?? 0 원하는 결과를 얻으려면?
Christoffer Lette

확실히 작동합니다. 방금 시도해보기 위해 단위 테스트를 빌드했지만 선택 결과가 빈 목록이 아닌 단일 null 값인 이유를 알아 내야합니다 (SQL 배경이 나를 혼란스럽게 할 수 있습니다 ). 감사합니다.
Chris Simpson

1
@Lette, 변경하면 decimal result1 = ..... Min () ?? 0; 이것도 작동하므로 귀하의 의견에 감사드립니다.
Chris Simpson

125

원하는 것은 다음과 같습니다.

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();

이제 깔끔합니다!


1
@ChristofferLette 나는 T의 빈 목록을 원하므로 Min ()과 함께 Any ()를 사용했습니다. 감사!
Adrian Marinica 2012-08-02

1
@AdrianMar : BTW, Null 개체 를 기본값으로 사용하는 것을 고려 했 습니까?
Christoffer Lette 2012-08-02

17
여기에 언급 된 MinOrDefault 구현은 열거 형을 두 번 반복합니다. 메모리 내 컬렉션에는 중요하지 않지만 LINQ to Entity 또는 지연된 "수익률 반환"빌드 열거 형의 경우 이는 데이터베이스로 두 번 왕복하거나 첫 번째 요소를 두 번 처리하는 것을 의미합니다. 나는 results.DefaultIfEmpty (myDefault) .Min () 솔루션을 선호합니다.
Kevin Coulombe 2013

4
의 소스를 보면 DefaultIfEmpty실제로 스마트하게 구현되어 yield returns를 사용하는 요소가있는 경우에만 시퀀스를 전달합니다 .
Peter Lillevold

2
@JDandChips는 형식에서 인용 DefaultIfEmpty하고 IEnumerable<T>있습니다. IQueryable<T>데이터베이스 작업에서와 같이 에서 호출 한 경우 단일 시퀀스를 반환하지 않지만 적절한을 생성 MethodCallExpression하므로 결과 쿼리에서 모든 항목을 검색 할 필요가 없습니다. EnumerableExtensions여기에 제안 된 접근 방식에는 그 문제가 있습니다.
Jon Hanna

16

이미 언급했듯이 소량의 코드로 한 번만 수행하는 가장 좋은 점은 다음과 같습니다.

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, doubledecimal세트에 맞게 Min()에서 제공을 Enumerable. 이것은 T4 템플릿이 유용한 종류입니다.

결국 우리는 MinOrDefault()다양한 유형에 대해 우리가 기대할 수 있는 것만 큼의 성능을 구현했습니다 . 확실히 한 번만 사용 DefaultIfEmpty().Min()하면 "단순"하지는 않지만 (다시 말하지만 ), 많이 사용하는 경우에는 "단순"합니다. 따라서 재사용 할 수있는 멋진 라이브러리가 있습니다. StackOverflow에 대한 답변…).


0

이 접근 방식은 Amount에서 가장 작은 값 하나를 반환합니다 itemList. 이론적으로이 해야 데이터베이스에 여러 개의 라운드 트립을 피할 수 있습니다.

decimal? result = (from Item itm in itemList
                  where itm.Amount > 0)
                 .Min(itm => (decimal?)itm.Amount);

nullable 형식을 사용하고 있기 때문에 null 참조 예외가 더 이상 발생하지 않습니다.

Any호출하기 전에 와 같은 실행 메소드의 사용을 피함으로써 Min데이터베이스로 한 번만 이동해야합니다.


1
Select수락 된 답변에서 의 사용이 쿼리를 두 번 이상 실행 한다고 생각하는 이유는 무엇입니까 ? 수락 된 응답은 단일 DB 호출이됩니다.
Jon Hanna

당신 말이 맞아요, Select지연된 메서드이고 실행을 일으키지 않을 것입니다. 나는 내 대답에서 이러한 거짓말을 제거했습니다. 참조 : Adam Freeman의 "Pro ASP.NET MVC4"(도서)
JDandChips

낭비가 없도록 정말로 낙관적으로 만들고 싶다면 방금 게시 한 답변을 살펴보십시오.
Jon Hanna

-1

itemList가 nullable이 아니고 (DefaultIfEmpty가 0을 제공하는 경우) 잠재적 출력 값으로 null을 원하는 경우 람다 구문도 사용할 수 있습니다.

decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.