6

同じIEnumerableに最大5つのFuncを累積的に適用するための良い方法を見つけようとしています。これが私が思いついたものです:

private Func<SurveyUserView,bool> _getFilterLambda(IDictionary<string, string> filters)
{
    Func<SurveyUserView, bool> invokeList = delegate(SurveyUserView surveyUserView)
    { 
        return surveyUserView.deleted != "deleted"; 
    };

    if (filters.ContainsKey("RegionFilter"))
    {
        invokeList += delegate(SurveyUserView surveyUserView)
        {
            return surveyUserView.Region == filters["RegionFilter"];
        };
    }

    if (filters.ContainsKey("LanguageFilter"))
    {
        invokeList += delegate(SurveyUserView surveyUserView)
        {
            return surveyUserView.Locale == filters["LanguageFilter"];
        };
    }

    if (filters.ContainsKey("StatusFilter"))
    {
        invokeList += delegate(SurveyUserView surveyUserView)
        { 
            return surveyUserView.Status == filters["StatusFilter"]; 
        };
    }

    if (filters.ContainsKey("DepartmentFilter"))
    {
        invokeList += delegate(SurveyUserView surveyUserView)
        {
            return surveyUserView.department == filters["DepartmentFilter"];
        };
    }

    return invokeList;
}

これらは累積的に適用されると思いましたが、結果から、実際には最後の1つ(DepartmentFilter)を適用しているだけであることがわかります。

2 ^ 4の可能な組み合わせがあるので、ブルートフォースif/elsesが機能しない場合。(対応するキーが辞書に存在する場合にのみ、特定のラムダをANDで使用したい。)

編集:これが私が受け入れた解決策ですが、評価されるとStackOverflowExceptionが発生します。誰かが理由がわかりますか?

private Func<SurveyUserView,bool> _getFilterLambda(IDictionary<string, string> filters )
    {

        Func<SurveyUserView, bool> resultFilter = (suv) => suv.deleted != "deleted";                                                        

        if (filters.ContainsKey("RegionFilter"))
        {
            Func<SurveyUserView, bool> newFilter =
                (suv) => resultFilter(suv) && suv.Region == filters["RegionFilter"];
            resultFilter = newFilter;
        }

        if (filters.ContainsKey("LanguageFilter"))
        {
            Func<SurveyUserView, bool> newFilter =
                 (suv) => resultFilter(suv) && suv.Locale == filters["LanguageFilter"];
            resultFilter = newFilter;
        }

        if (filters.ContainsKey("StatusFilter"))
        {
            Func<SurveyUserView, bool> newFilter =
                (suv) => resultFilter(suv) && suv.Status == filters["StatusFilter"];
            resultFilter = newFilter;
        }

        if (filters.ContainsKey("DepartmentFilter"))
        {
            Func<SurveyUserView, bool> newFilter =
                (suv) => resultFilter(suv) && suv.department == filters["DepartmentFilter"];
            resultFilter = newFilter;
        }

        return resultFilter;
    }

編集:これが、友人でありメンターであるChrisFlatherからStackOverflowExceptionが発生した理由の非常に優れた説明です-

無限再帰が発生する理由を理解するための重要なことは、ラムダ内のシンボルがいつ解決されるかを理解することです(つまり、実行時であり、定義時ではありません)。

この単純化された例を見てください:

Func<int, int> demo = (x) => x * 2;
Func<int, int> demo2 = (y) => demo(y) + 1;
demo = demo2;
int count = demo(1);

定義時に静的に解決された場合、これは機能し、次のようになります。

Func<int, int> demo2 = (y) => (y * 2) + 1;
Int count = demo2(1);

ただし、demo2に埋め込まれたデモが実行時に何をするかを実際に理解しようとはしません。実行時に、demo2はdemoに再定義されます。基本的に、コードは次のようになります。

Func<int, int> demo2 = (y) => demo2(y) + 1;
Int count = demo2(1);
4

4 に答える 4

4

この方法でデリゲートを結合しようとする代わりに、既存のデリゲートをAND条件で使用する新しいデリゲートを作成できます。

Func<SurveyUserView, bool> resultFilter = (suv) => true;

if (filters.ContainsKey("RegionFilter"))
{
    var tmpFilter = resultFilter;
    // Create a new Func based on the old + new condition
    resultFilter = (suv) => tmpFilter(suv) && suv.Region == filters["RegionFilter"];
}

if (filters.ContainsKey("LanguageFilter"))
{
   // Same as above...

//... Continue, then:

return resultFilter;

そうは言っても、元のメソッドIQueryable<SurveyUserView>またはこのメソッドに渡し、句を直接フィルターIEnumerable<SurveyUserView>に追加する方が簡単な場合があります。.Whereその後、フィルターを追加して、実行せずに最終的なクエリを返すことができます。

于 2012-06-01T02:13:51.677 に答える
2

Where(...)おそらく、であるものに拡張機能を使用して、:の代わりにをIQueryable<SurveyUserView>返すと思います:IQueryable<SurveyUserView>Func<...>

// Assuming `q` is a `IQueryable<SurveyUserView>`

if(filters.ContainsKeys["Whatever"])
{
  q = q.Where(suv => suv.Status == filters["Whatever"];
}

Andingは暗黙的です。

于 2012-06-01T02:18:23.190 に答える
2
    private Func<SurveyUserView, bool> _getFilterLabda(IDictionary<string, string> filters)
    {
        Func<SurveyUserView, bool> invokeList = surveyUserView => surveyUserView.deleted != "deleted");

        if (filters.ContainsKey("RegionFilter"))
        {
            invokeList += surveyUserView => surveyUserView.Region == filters["RegionFilter"]);
        }

        if (filters.ContainsKey("LanguageFilter"))
        {
            invokeList += surveyUserView => surveyUserView.Locale == filters["LanguageFilter"];
        }

        if (filters.ContainsKey("StatusFilter"))
        {
            invokeList += surveyUserView => surveyUserView.Status == filters["StatusFilter"];
        }

        if (filters.ContainsKey("DepartmentFilter"))
        {
            invokeList += surveyUserView => surveyUserView.department == filters["DepartmentFilter"]);
        }

        return invokeList;
    }
    ...
    Func<SurveyUserView, bool> resultFilter = suv => _getFilterLabda(filters)
         .GetInvocationList()
         .Cast<Func<SurveyUserView, bool>>()
         .All(del => del(suv))
于 2012-06-01T03:01:40.767 に答える
1

これが、あなたが求めていることを達成するための私のお気に入りの方法です。

private Func<SurveyUserView, bool> _getFilterLambda(IDictionary<string, string> filters)
{
    List<Func<SurveyUserView, bool>> invokeList = new List<Func<SurveyUserView, bool>>();

    invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.deleted != "deleted");

    if (filters.ContainsKey("RegionFilter"))
    {
        invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Region == filters["RegionFilter"]);
    }

    if (filters.ContainsKey("LanguageFilter"))
    {
        invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Locale == filters["LanguageFilter"]);
    }

    if (filters.ContainsKey("StatusFilter"))
    {
        invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Status == filters["StatusFilter"]);
    }

    if (filters.ContainsKey("DepartmentFilter"))
    {
        invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.department == filters["DepartmentFilter"]);
    }

    return delegate (SurveyUserView surveyUserView)
    {
        bool unfiltered = true;

        foreach(var filter in invokeList)
        {
            unfiltered = unfiltered && filter(surveyUserView);
        }

        return unfiltered;
    };
}

申請する代理人のリストを作成します。次に、各フィルターを単純な論理 AND で組み合わせて、そのリストを反復処理する別の別のデリゲートを返します。

これが機能するのは、返されるデリゲートが invokeList を閉じるためです。返されたデリゲートと共に移動するすべての新しいデリゲートを格納する一種のプライベート変数を作成します。

元の構文にわずかに近い代替手段は次のとおりです。

private Func<SurveyUserView, bool> _getFilterLambda(IDictionary<string, string> filters)
{
    Func<SurveyUserView, bool> invokeList = (SurveyUserView surveyUserView) => surveyUserView.deleted != "deleted";

    if (filters.ContainsKey("RegionFilter"))
    {
        invokeList += (SurveyUserView surveyUserView) => surveyUserView.Region == filters["RegionFilter"];
    }

    if (filters.ContainsKey("LanguageFilter"))
    {
        invokeList += (SurveyUserView surveyUserView) => surveyUserView.Locale == filters["LanguageFilter"];
    }

    if (filters.ContainsKey("StatusFilter"))
    {
        invokeList += (SurveyUserView surveyUserView) => surveyUserView.Status == filters["StatusFilter"];
    }

    if (filters.ContainsKey("DepartmentFilter"))
    {
        invokeList += (SurveyUserView surveyUserView) => surveyUserView.department == filters["DepartmentFilter"];
    }

    return delegate (SurveyUserView surveyUserView)
    {
        bool unfiltered = true;

        // implicit cast from Delegate to Func<SurveyUserView, bool> happening on next line
        foreach (Func<SurveyUserView, bool> filter in invokeList.GetInvocationList())
        {
            unfiltered = unfiltered && filter(surveyUserView);
        }

        return unfiltered;
    };
}

このバージョンでは、デリゲートのリストとして単に invokeList を使用しています。GetInvocationList() (Func が派生する Delegate クラスのメソッド) を呼び出して、マルチキャスト デリゲートを作成するために結合されたすべてのデリゲートのリストを取得します。

舞台裏で何が起こっているかがより明確になるので、個人的には最初のバージョンの方が好きです。

これらはどちらも、応答する前に何らかの形で見逃したJacob Seleznev の回答と実際には同じです。メソッド自体が Trey の元のコントラクトを満たすように、最終的なデリゲートをメソッド内に持ってくるだけです。

最後に、すべてのフィルターが順序に依存せず、副作用がない場合、フィルターを並行して実行するバージョンを作成できます。

private Func<SurveyUserView, bool> _getFilterLambdaParallel(IDictionary<string, string> filters)
{
    List<Func<SurveyUserView, bool>> invokeList = new List<Func<SurveyUserView, bool>>();

    invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.deleted != "deleted");

    if (filters.ContainsKey("RegionFilter"))
    {
        invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Region == filters["RegionFilter"]);
    }

    if (filters.ContainsKey("LanguageFilter"))
    {
        invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Locale == filters["LanguageFilter"]);
    }

    if (filters.ContainsKey("StatusFilter"))
    {
        invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Status == filters["StatusFilter"]);
    }

    if (filters.ContainsKey("DepartmentFilter"))
    {
        invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.department == filters["DepartmentFilter"]);
    }

    return delegate (SurveyUserView surveyUserView)
    {
        int okCount = 0;
        Parallel.ForEach(invokeList, delegate (Func<SurveyUserView, bool> f)
        {
            if (f(surveyUserView))
            {
                System.Threading.Interlocked.Increment(ref okCount);
            }
        });
        return okCount == invokeList.Count;
    };
}

Parallel.ForEach を使用して、フィルターを並列で実行します。単純なブール AND を使用することを妨げるわずかな複雑さがあります。論理 AND がアトミックに発生し、厄介な競合状態が発生するという保証はありません。

これを修正するには、アトミックであることが保証されている Interlocked.Increment を使用して、通過したフィルターの数をカウントするだけです。すべてのフィルターが正常に通過した場合、true を返すことができることがわかります。そうしないと、 and は失敗します。

ここで論理 OR を実行するのと同等のことは、okCount が 0 より大きいことを確認する必要があります。

于 2014-07-15T05:59:29.357 に答える