2010-03-05 4 views
10

HINWEIS: Kurz vor dieser Frage Entsendung es fiel mir ein, gibt es eine bessere Art und Weise zu tun, was ich (und ich fühle mich ziemlich dumm darüber) zu erreichen versuchte:Was fehlt mir in dieser Kette von Prädikaten?

IEnumerable<string> checkedItems = ProductTypesList.CheckedItems.Cast<string>(); 
filter = p => checkedItems.Contains(p.ProductType); 

So OK, ja, ich erkenne das schon. Wie auch immer, ich poste die Frage trotzdem, weil ich immer noch nicht ganz verstehe, warum was ich war (dumm) versuchte, zu tun, funktionierte nicht.


Ich dachte, das wäre extrem einfach. Stellt sich heraus, dass es mir ziemlich Kopfschmerzen bereitet.

Die Grundidee: Zeigen Sie alle Elemente an, deren ProductType Eigenschaftswert in einem CheckedListBox überprüft wird.

Die Umsetzung:

private Func<Product, bool> GetProductTypeFilter() { 
    // if nothing is checked, display nothing 
    Func<Product, bool> filter = p => false; 

    foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) { 
     Func<Product, bool> prevFilter = filter; 
     filter = p => (prevFilter(p) || p.ProductType == pt); 
    } 

    return filter; 
} 

Jedoch sagt die Elemente "Equity" und "ETF" wird überprüft sowohl in ProductTypesList (a CheckedListBox). Dann aus irgendeinem Grunde nur der folgende Code gibt Produkte vom Typ „ETF“:

var filter = GetProductTypeFilter(); 
IEnumerable<Product> filteredProducts = allProducts.Where(filter); 

Ich vermuten, es könnte etwas gehabt hat, mit einiger selbst verweisende Unsauberkeit zu tun, wo filter ist auf im Wesentlichen selbst oder etwas anderes. Und ich dachte, dass vielleicht mit ...

filter = new Func<Product, bool>(p => (prevFilter(p) || p.ProductType == pt)); 

... würde den Trick tun, aber kein solches Glück. Kann jemand sehen, was ich hier vermisse?

Antwort

9

Ich glaube, dass Sie hier ein Problem mit einem modifizierten Abschluss haben. Der Parameter pt ist an den Lambda-Ausdruck gebunden, ändert sich jedoch im Verlauf der Schleife. Es ist wichtig zu wissen, dass wenn eine Variable in einem Lambda referenziert wird, ist es die Variable, die erfasst wird, nicht der Wert der Variablen.

In Schleifen hat dies eine sehr wichtige Verzweigung - weil sich die Schleifenvariable ändert und nicht neu definiert wird. Durch das Erstellen einer Variablen innerhalb der Schleife erstellen Sie eine neue Variable für jede Iteration - was dann zulässt, dass das Lambda jedes unabhängig erfasst.

würde die gewünschte Umsetzung sein:

A:

foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) { 
    string ptCheck = pt; 
    Func<Product, bool> prevFilter = filter; 
    filter = p => (prevFilter(p) || p.ProductType == ptCheck); 
} 

Eric Lippert über diese spezifische Situation geschrieben hat Siehe auch die Frage Access to Modified Closure (2) für eine gute Erklärung dessen, was mit Abschlussvariablen passiert.Es gibt auch eine Reihe von Artikeln auf dem Blog The Old New Thing, die eine interessante Perspektive auf dieses hat:

+0

Nun, das macht Sinn. Vielen Dank! –

2

Es hat mit Schließungen zu tun . Die Variable pt bezieht sich immer auf den letzten Wert der for-Schleife.

Betrachten Sie das folgende Beispiel, in dem die Ausgabe erwartet wird, da sie eine Variable verwendet, die innerhalb der for-Schleife liegt.

public static void Main(string[] args) 
{ 
    var countries = new List<string>() { "pt", "en", "sp" }; 

    var filter = GetFilter(); 

    Console.WriteLine(String.Join(", ", countries.Where(filter).ToArray())); 
} 

private static Func<string, bool> GetFilter() 
{ 
    Func<string, bool> filter = p => false; 

    foreach (string pt in new string[] { "pt", "en" }) 
    { 
     Func<string, bool> prevFilter = filter; 

     string name = pt; 

     filter = p => (prevFilter(p) || p == name); 
    } 

    return filter; 
} 
2

Da Sie Looping und den Filtertyp an sich selbst zu setzen, haben Sie den Produkttyp zur letzten pt jeweils festlegen. Es ist ein modifizierter Verschluss und da es Verzögerung gebunden, müssen Sie es auf jeder Schleife kopieren, wie folgt aus:

foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) { 
    var mypt = pt; 
    Func<Product, bool> prevFilter = filter; 
    filter = p => (prevFilter(p) || p.ProductType == mypt); 
} 

Dies richtigen Ergebnis führen sollte, da sonst die letzte pt für alle Gleichheit Prüfungen verwendet wird.

Verwandte Themen