2009-04-16 18 views
3

Ich versuche, einen dynamischen Filter mit einer Kombination von Lambda-Funktionen zu erstellen. Die Anzahl der Lambda-Funktionen kann jedoch abhängig von der Anzahl der verschiedenen Filter variieren, die der Benutzer anwendet.Kombinieren mehrerer Lambda-Funktionen zusammen mit effizienter Ausführung

ich etwas, das wie dieses

//images is a List<ImageInfo> 
var result1 = images 
.Where(image => image.Bytes < 1000) 
.Where(image => image.Height < 100) 
.Where(image => image.Width < 100); 

verhält Wo die Höhe Filter nur auf jene Bilder, die die Bytes Filter passieren angewendet wird. Und der Breitenfilter wird nur von solchen Bildern angewendet, die den Höhenfilter passieren.

Aber die dynamische Art und Weise, wie die Filter von den Benutzern ein- und ausgeschaltet werden, erlaubt mir nicht, eine einzige Lambda-Funktion zu erstellen.

Stattdessen werde ich eine Liste von Lambda-Funktionen erstellen und sie dann auf die Liste der Bilder anwenden. So würde ich mit so etwas enden; mehrere individuelle Lambda-Funktionen.

var filter1 = images.Where(image => image.Bytes < 1000); 
var filter2 = images.Where(image => image.Height < 100); 
var filter3 = images.Where(image => image.Width < 100); 

Wie kann ich mehrere Lambda-Funktionen zusammenfügen, um meine endgültige Liste gefilterter Bilder zu erhalten?

Ich habe diese

var result = filter1.Intersect<ImageInfo>(filter2).Intersect<ImageInfo>(filter3); 

Aber jeder Filter dreht sich durch die Hauptliste der Bilder seine Teilmenge der Bilder zu bekommen, und dann tut eine Kreuzung Berechnung, die viel zu viel CPU nimmt.

Also was ich suche ist eine Möglichkeit, eine beliebige Liste von Lambda-Funktionen (Ausdrücke ... was auch immer) zu nehmen und sie auf eine Weise zu verbinden, die so ausgeführt wird, wie das erste Beispiel, das ich zeigte.

Antwort

0

warum wenden Sie nicht einfach die Funktionen nacheinander an, wie im ersten Beispiel? auf das Ergebnis des letzten

Youre Filter haben die Unterschrift von

Func<IEnumerable<ImageInfo>, IEnumerable<ImageInfo>> 

so einfach jeden Filter anwenden?

so?

IEnumerable<ImageInfo> filtered = images; 

if(filterByBytes) 
    filtered = filtered.Where(image => image.Bytes < 1000); 

if(filterByHeight) 
    filtered = filtered.Where(image => image.Height < 100); 

if(filterByWidth) 
    filtered = filtered.Where(image => image.Width < 100); 

bearbeiten re: Kommentare, aus der Spitze von meinem Kopf, so etwas wie ...

List<Func<IEnumerable<ImageInfo>, IEnumerable<ImageInfo>>> lambdas = new List<Func<IEnumerable<ImageInfo>, IEnumerable<ImageInfo>>>(); 

lambdas.add(x => x.Where(image => image.Bytes < 1000)); 
lambdas.add(x => x.Where(image => image.Height < 100)); 
lambdas.add(x => x.Where(image => image.Width < 100)); 

foreach(var lambda in lambdas) 
    images = lamdba.Invoke(images); 
+0

Es stimmt, ich könnte das tun, aber weil die Liste der Filter ziemlich dynamisch ist (neue jederzeit von jedem hinzugefügt werden könnte Quelle) Ich möchte die Lambda-Funktion in einer Liste von Lambda-Funktionen speichern, und dann durch die Liste drehen und sie einzeln anwenden. –

+0

Wie speichern Sie eine Lambda-Funktion als Variable? –

+0

Denken Sie daran, dass Lambdas nur Delegaten sind (ein Funktionsaufruf). Von meiner Antwort "Func , IEnumerable >" das ist der Typ für einen Delegaten, der ein IEnum von imageinfo nimmt und dasselbe zurückgibt –

3

Okay, wie etwa:

static Func<T, bool> CombineWithAnd<T>(IEnumerable<Func<T, bool>> filters) 
{ 
    return x => 
    { 
     foreach (var filter in filters) 
     { 
      if (!filter(x)) 
      { 
       return false; 
      } 
     } 
     return true; 
    }; 
} 

Ist das, was Sie nach ? Grundsätzlich wird die Menge der Filter innerhalb des Lambda-Ausdrucks erfasst und nacheinander angewendet (mit Kurzschluss), wenn das zurückgegebene Lambda verwendet wird. Offensichtlich konnte man das gleiche in einer „Oder“ Art und Weise tun zu:

static Func<T, bool> CombineWithOr<T>(IEnumerable<Func<T, bool>> filters) 
{ 
    return x => 
    { 
     foreach (var filter in filters) 
     { 
      if (filter(x)) 
      { 
       return true; 
      } 
     } 
     return false; 
    }; 
} 

Beachten Sie, dass, wenn die Sammlung von Filtern nach dem Aufruf der Methode geändert wird (zB es ein List<T> ist und Sie einen neuen Filter hinzufügen), dann die zurückgegebene " Kombination "Filter wird diese Änderungen widerspiegeln.Wenn Sie dieses Verhalten nicht möchten, fügen Sie hinzu:

filters = filters.ToList(); 

als die erste Zeile der Methode, um effektiv eine Kopie zu nehmen. Beachten Sie, dass die Delegaten unveränderlich sind. Sie müssen sich also keine Gedanken darüber machen, ob sich die Änderungen ändern.

1

Oh süß! Du warst sehr nah dran, aber nahe genug, um mir die Antwort zu geben. Um alle Filter anzuwenden, muss sich die return true außerhalb der foreach-Schleife bewegen (siehe unten). Aber genau das suche ich.

Eine Frage oder ein Kommentar. Was mich wirklich an diese Funktion gebracht hat, war die Variable x. Ich musste laufen und den Code debuggen, um herauszufinden, dass das x vom Typ <T> sein würde. Ich habe noch nie eine Variable gesehen, die vorher noch keine Typ- oder Var-Deklaration hatte und das hat mich wirklich gestoßen. Können Sie die C# -Regeln etwas erklären, um die Variable x ohne irgendeine Deklaration zuzulassen?

Sehr elegante Lösung übrigens.

static Func<T, bool> CombineWithAnd<T>(IEnumerable<Func<T, bool>> filters) 
{ 
    return x => 
    { 
     foreach (var filter in filters) 
     { 
      if (!filter(x)) 
      { 
       return false; 
      } 
     } 
     return true; 
    }; 
} 
+0

Doh, danke, reparierte meine Antwort. Die Regeln um die Parameter für Lambda-Ausdrücke bestehen im Wesentlichen darin, dass, wenn der Compiler seine Typen basierend darauf ausarbeiten kann, in was der Lambda-Ausdruck konvertiert werden soll, müssen Sie die Typen nicht explizit angeben. –

Verwandte Themen