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

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, doubledecimal세트에 맞게 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);


답변