Linqで「MinOrDefault」を実現するための最も簡単な方法は何ですか?


82

linq式から10進値のリストを作成していますが、ゼロ以外の最小値が必要です。ただし、linq式によってリストが空になる可能性は十分にあります。

これにより例外が発生し、この状況に対処するMinOrDefaultはありません。

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

リストが空の場合に結果を0に設定する最も簡単な方法は何ですか?


9
MinOrDefault()をライブラリに追加することを提案するための+1。
J. Andrew Laughlin 2013

回答:


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

への変換に注意してくださいdecimal?。何もない場合は空の結果が得られます(事後に処理するだけです。主に例外を停止する方法を説明しています)。また、!=ではなく「ゼロ以外」を使用しました>


面白い。これで空のリストを回避する方法を理解することはできませんが、試してみます
Chris Simpson

7
試してみてください:decimal? result = (new decimal?[0]).Min();与えるnull
MarcGravell

2
そしておそらくそれから?? 望ましい結果を得るために0?
Christoffer Lette 2010年

それは間違いなく機能します。ユニットテストを作成して試してみましたが、選択の結果が空のリストではなく単一のnull値になる理由を理解するのに5分かかる必要があります(私のSQLの背景が私を混乱させる可能性があります)。これをありがとう。
クリスシンプソン

1
@Lette、次のように変更した場合:decimal result1 = ..... Min()?? 0; これも機能するので、ご入力いただきありがとうございます。
クリスシンプソン

126

あなたが欲しいのはこれです:

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()1つの要素を含むシーケンスを生成しdefault(T)ます---後で呼び出すことができますMin()

default(T)が希望どおりでない場合は、次の方法で独自のデフォルトを指定できます。

double myDefault = ...
double result = results.DefaultIfEmpty(myDefault).Min();

さて、それはきちんとしています!


1
@ChristofferLette Tの空のリストだけが必要なので、Min()でAny()を使用することになりました。ありがとう!
エイドリアンマリニカ2012

1
@AdrianMar:ところで、デフォルトとしてNullオブジェクトを使用することを検討しましたか?
Christoffer Lette 2012

17
ここで説明するMinOrDefaultの実装は、列挙可能なものを2回繰り返します。インメモリコレクションでは問題ありませんが、LINQ to Entityまたは遅延「yieldreturn」で構築された列挙可能オブジェクトの場合、これはデータベースへの2回のラウンドトリップ、または最初の要素の2回の処理を意味します。私はresults.DefaultIfEmpty(myDefault).Min()ソリューションを好みます。
Kevin Coulombe 2013

4
のソースを見ると、DefaultIfEmpty実際にスマートに実装されており、yield returnsを使用する要素がある場合にのみシーケンスの転送を行います。
Peter Lillevold 2014年

2
その形式から引用している@JDandChipsDefaultIfEmptyIEnumerable<T>。を取ります。IQueryable<T>データベース操作の場合のように、で呼び出した場合、シングルトンシーケンスは返されませんが、適切なが生成されるMethodCallExpressionため、結果のクエリですべてを取得する必要はありません。EnumerableExtensionsただし、ここで提案されているアプローチにはその問題があります。
ジョンハンナ2015年

16

すでに述べたように、少量のコードで1回だけ実行するという点で、最も簡単な方法は次のとおりです。

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他のが呼び出された場合、すでに定義されている動作から必要な動作を取得します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();
}

そして、そのためにはlongfloatdoubledecimalのセットと一致することMin()により提供しますEnumerable。これは、T4テンプレートが役立つようなものです。

そのすべての終わりに、私たちはMinOrDefault()、幅広いタイプに対して、私たちが期待できるのとほぼ同じくらいパフォーマンスの高い実装を持っています。確かに、1回の使用に直面して「きちんと」ではありませんが(繰り返しますが、単に使用しますDefaultIfEmpty().Min())、それを頻繁に使用していることがわかった場合は非常に「きちんと」なるので、再利用できる(または実際に貼り付けることができる)素晴らしいライブラリがありますStackOverflowでの回答…)。


0

このアプローチは、Amountから単一の最小値を返しますitemList。理論的にはこれは必要があり、データベースへの複数のラウンドトリップを回避します。

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

null許容型を使用しているため、null参照例外は発生しなくなりました。

Any呼び出す前などの実行メソッドの使用を回避することによりMin、データベースに1回だけアクセスする必要があります。


1
Select受け入れられた回答での使用がクエリを複数回実行すると思われる理由は何ですか?受け入れられた回答は、単一のDB呼び出しになります。
ジョンハンナ

そうですSelect、延期されたメソッドであり、実行を引き起こしません。私はこれらの嘘を私の答えから削除しました。参照:AdamFreemanによる「ProASP.NETMVC4」(本)
JDandChips 2015年

無駄がないことを確認するのに本当に強気になりたい場合は、私が投稿したばかりの回答を見てください。
ジョンハンナ

-1

itemListがnull不可(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.