IT TIP

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

itqueen 2020. 10. 20. 19:01
반응형

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 템플릿이 유용한 종류입니다.

At the end of all that, we have just about as performant an implementation of MinOrDefault() as we could hope for, for a wide range of types. Certainly not "neat" in the face of one use for it (again, just use DefaultIfEmpty().Min()), but very much "neat" if we find ourselves using it a lot, so we have a nice library we can reuse (or indeed, paste into answers on StackOverflow…).


This approach will return the single smallest Amount value from itemList. In theory this should avoid multiple round trips to the database.

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

The null reference exception is no longer caused because we are using a nullable type.

By avoiding the use of executing methods such as Any before calling Min, we should only be making one trip to the database

참고URL : https://stackoverflow.com/questions/2165605/whats-the-neatest-way-to-achieve-minordefault-in-linq

반응형