すでに述べたように、少量のコードで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)
{
var result = source.Min();
return result == null ? defaultValue : result;
}
else
{
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()
、幅広いタイプに対して、私たちが期待できるのとほぼ同じくらいパフォーマンスの高い実装を持っています。確かに、1回の使用に直面して「きちんと」ではありませんが(繰り返しますが、単に使用しますDefaultIfEmpty().Min()
)、それを頻繁に使用していることがわかった場合は非常に「きちんと」なるので、再利用できる(または実際に貼り付けることができる)素晴らしいライブラリがありますStackOverflowでの回答…)。