10

:この質問を投稿する直前に、私が達成しようとしていたことを行うためのより良い方法があることに気づきました(そして、私はそれについてかなり愚かだと感じています):

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

はい、そうです、私はすでにこれを認識しています。ただし、とにかく質問を投稿しています。なぜなら、私が(愚かに)やろうとしていたことがうまくいかなかった理由がまだよくわからないからです。


これは非常に簡単だと思いました。それは私にかなりの頭痛を与えていることがわかりました。

ProductType基本的な考え方: でプロパティ値がチェックされているすべてのアイテムを表示しますCheckedListBox

実装:

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

ただし、項目「Equity」と「ETF」の両方がチェックインされているとしますProductTypesList(a CheckedListBox)。その後、何らかの理由で、次のコードは「ETF」タイプの商品のみを返します。

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

filter本質的にそれ自体または他の何かに設定されている自己参照の混乱と関係があるのではないかと思いました。そして、私はおそらく使用していると思いました...

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

...うまくいきますが、そのような運はありません。ここで私が見逃しているものを見ることができますか?

4

3 に答える 3

9

ここで閉鎖の問題が修正されたと思います。ptパラメータはラムダ式にバインドされていますが、ループが進行するにつれて変化します。変数がラムダで参照される場合、変数の値ではなく、変数がキャプチャされることを理解することが重要です。

ループでは、これは非常に重要な影響を及ぼします - ループ変数が変更され、再定義されないからです。loop 内に変数を作成することにより、反復ごとに新しい変数を作成します。これにより、ラムダがそれぞ​​れを個別にキャプチャできるようになります。

望ましい実装は次のとおりです。

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 は、この特定の状況について次のように書いています。

また、クロージャー変数で何が起こるかについての適切な説明については、変更されたクロージャーへのアクセス (2)の質問を参照してください。ブログThe Old New Thingには、これについて興味深い視点を持つ一連の記事もあります。

于 2010-03-05T20:06:38.267 に答える
2

それは閉鎖と関係があります。変数 pt は、常に for ループの最後の値を参照します。

for ループ内でスコープが設定された変数を使用しているため、出力が期待される次の例を考えてみましょう。

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;
}
于 2010-03-05T20:06:58.927 に答える
2

ループしてフィルター タイプをそれ自体に設定しているため、ptいずれの場合も製品タイプを最後に設定しています。これは変更されたクロージャであり、遅延が制限されているため、次のようにループごとにコピーする必要があります。

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

これにより正しい結果が得られるはずです。そうでない場合は、最後の結果ptがすべての同等性チェックに使用されます。

于 2010-03-05T20:07:19.200 に答える