需要&amp;&amp;一起不确定数量的Func <tentity,bool =“”> </tentity,>

时间:2012-06-01 02:09:03

标签: c# delegates lambda

我试图找到一种好方法,将最多5个Func累积应用到同一个IEnumerable。以下是我提出的建议:

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

我认为它会以累积方式应用这些,但是,我可以从结果中看出它实际上只是应用了最后一个(DepartmentFilter)。

有2 ^ 4种可能的组合,所以如果/ elses不起作用那么强力。 (我希望仅在字典中存在相应的键时使用特定的lambda。)

编辑: 这是我接受的解决方案,但在评估时会导致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;
    }

编辑: 以下是为什么这会导致朋友和导师Chris Flather发生StackOverflow异常的非常好的解释 -

理解无限递归发生的重要一点是理解lambda中的符号何时被解析(即在运行时而不是在定义时)。

采用这个简化的例子:

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 个答案:

答案 0 :(得分: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子句直接添加到过滤器即可。然后,您可以在不执行的情况下返回最终查询,并添加过滤器。

答案 1 :(得分: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"];
}

And是隐含的。

答案 2 :(得分: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))

答案 3 :(得分: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()(Delegate类Func派生自的方法)来获取组合起来构成多播委托的所有委托的列表。

我个人更喜欢第一个版本,因为它更清楚幕后发生的事情。

这两个都与Jacob Seleznev's answer真的相同,我在回答之前错过了一些。他们只是将最终代表带入方法中,以便方法本身仍然满足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将以原子方式发生,从而产生令人讨厌的竞争条件。

为了解决这个问题,我们只计算使用Interlocked.Increment传递的过滤器数量,这个过滤器保证是原子的。如果所有过滤器成功通过,那么我们知道我们可以返回true;否则就会失败。

在这里进行逻辑OR的等价物必须用okCount检查大于零。