103 Stimmen

Wie erreicht man "MinOrDefault" in LINQ?

Ich erzeuge eine Liste von Dezimalwerten aus einem LINQ-Ausdruck und möchte den kleinsten Wert, der nicht Null ist. Es ist jedoch durchaus möglich, dass der LINQ-Ausdruck zu einer leeren Liste führt.

Dies führt zu einer Ausnahme, und es gibt kein MinOrDefault, um mit dieser Situation umzugehen.

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

Wie kann ich das Ergebnis auf 0 setzen, wenn die Liste leer ist?

160voto

Christoffer Lette Punkte 13576

Was Sie wollen, ist dies:

IEnumerable<double> results = ... your query ...

double result = results.MinOrDefault();

Nun, MinOrDefault() gibt es nicht. Aber wenn wir es selbst implementieren würden, würde es in etwa so aussehen:

public static class EnumerableExtensions
{
    public static T MinOrDefault<T>(this IEnumerable<T> sequence)
    {
        if (sequence.Any())
        {
            return sequence.Min();
        }
        else
        {
            return default(T);
        }
    }
}

Es gibt jedoch eine Funktionalität in System.Linq die das gleiche Ergebnis (auf etwas andere Weise) erzielen:

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

Wenn die results Sequenz enthält keine Elemente, DefaultIfEmpty() erzeugt eine Sequenz, die ein Element enthält - die default(T) - die Sie anschließend aufrufen können Min() an.

Wenn die default(T) nicht das ist, was Sie wollen, dann können Sie Ihren eigenen Standard mit angeben:

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

Na, das ist ja toll!

63voto

Marc Gravell Punkte 970173
decimal? result = (from Item itm in itemList
                  where itm.Amount != 0
                  select (decimal?)itm.Amount).Min();

Beachten Sie die Umwandlung in decimal? . Sie erhalten ein leeres Ergebnis, wenn es keine gibt (behandeln Sie das einfach im Nachhinein - ich zeige hauptsächlich, wie man die Ausnahme stoppt). Ich habe auch "Nicht-Null" verwendet != statt > .

24voto

Jon Hanna Punkte 106367

Die sauberste in Bezug auf die nur einmal tun es in einer kleinen Menge Code ist, wie bereits erwähnt:

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

Mit Guss itm.Amount zu decimal? und die Erlangung der Min Das ist das Schönste, wenn wir diesen leeren Zustand erkennen wollen.

Wenn Sie jedoch tatsächlich eine MinOrDefault() dann können wir natürlich damit beginnen:

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

Sie haben jetzt einen vollständigen Satz von MinOrDefault ob Sie einen Selektor einschließen oder nicht und ob Sie den Standardwert angeben oder nicht.

Von diesem Punkt an ist Ihr Code einfach:

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

Am Anfang ist es zwar nicht so ordentlich, aber von da an ist es ordentlicher.

Doch halt! Es gibt noch mehr!

Nehmen wir an, Sie verwenden EF und möchten die async Unterstützung. Das ist ganz einfach:

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

(Beachten Sie, dass ich keine await hier; wir können direkt eine Task<TSource> die das, was wir brauchen, auch ohne sie tut, und somit die versteckten Komplikationen vermeidet await bringt).

Aber halt, da ist noch mehr! Nehmen wir an, wir verwenden dies mit IEnumerable<T> einige Male. Unser Ansatz ist suboptimal. Sicherlich können wir es besser machen!

Erstens, die Min festgelegt am int? , long? , float? double? und decimal? ohnehin schon das tun, was wir wollen (wie in der Antwort von Marc Gravell). In ähnlicher Weise erhalten wir das gewünschte Verhalten auch von der Min bereits definiert, wenn sie für eine andere T? . Lassen Sie uns also einige kleine und daher leicht einzubindende Methoden entwickeln, um diese Tatsache auszunutzen:

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

Beginnen wir zunächst mit dem allgemeineren Fall:

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;
}

Nun zu den offensichtlichen Überschreibungen, die sich dies zunutze machen:

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

Wenn es uns wirklich auf die Leistung ankommt, können wir für bestimmte Fälle optimieren, so wie Enumerable.Min() tut:

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

Und so weiter für long , float , double und decimal mit dem Satz von Min() zur Verfügung gestellt von Enumerable . Dies ist die Art von Dingen, bei denen T4-Vorlagen nützlich sind.

Am Ende haben wir eine fast ebenso leistungsfähige Implementierung von MinOrDefault() wie wir es uns erhoffen konnten, für eine breite Palette von Typen. Sicherlich nicht "ordentlich" angesichts der einen Verwendung dafür (wieder, verwenden Sie einfach DefaultIfEmpty().Min() ), aber sehr "ordentlich", wenn wir es oft verwenden, so dass wir eine schöne Bibliothek haben, die wir wiederverwenden können (oder in Antworten auf StackOverflow einfügen können ).

1voto

JDandChips Punkte 8991

Dieser Ansatz liefert die kleinste einzelne Amount Wert von itemList . Theoretisch ist dies sollte Vermeidung von Mehrfachabfragen in der Datenbank.

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

Die Null-Referenz-Ausnahme wird nicht mehr verursacht, da wir einen nullbaren Typ verwenden.

Durch die Vermeidung der Verwendung von Ausführungsmethoden wie Any vor dem Aufruf Min sollten wir nur einmal in der Datenbank nachsehen.

0voto

Jason Punkte 11

Wenn itemList nicht nullbar ist (wobei DefaultIfEmpty 0 ergibt) und Sie null als möglichen Ausgabewert wünschen, können Sie auch die Lambda-Syntax verwenden:

decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X