将多个lambda函数组合在一起并实现高效执行

时间:2009-04-16 15:47:50

标签: c# lambda

我正在尝试使用lambda函数的组合创建动态过滤器。但lambda函数的数量可能会根据用户应用的不同过滤器的数量而有所不同。

我想要一些表现得像这样的东西

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

高度过滤器仅应用于通过字节过滤器的图像。宽度过滤器仅适用于通过高度过滤器的图像。

但是,用户如何打开和关闭过滤器的动态方式不允许我创建一个lambda函数。

相反,我将创建一个lambda函数列表,然后需要将它们应用于图像列表。所以我最终得到这样的东西;多个单独的lambda函数。

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

如何将多个lambda函数连接在一起以获取最终的过滤图像列表?

我做了这个

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

但是每个滤镜都会旋转主要的图像列表以获取其图像子集,然后进行交叉计算,这会占用过多的CPU。

所以我正在寻找的是一种方法来获取lambda函数的任意列表(表达式...无论如何)并以一种方式加入它们,这种方式可以像我展示的第一个例子那样执行。

4 个答案:

答案 0 :(得分:3)

好的,怎么样:

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

这就是你要追求的吗?基本上,它捕获lambda表达式中的一组过滤器,并在使用返回的lambda时一个接一个地应用它们(带短路)。显然你也可以用“或”的方式做同样的事情:

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

请注意,如果在调用方法后修改过滤器集合(例如,它是List<T>并添加新过滤器),则返回的“组合”过滤器将反映这些更改。如果您不想要这种行为,请添加:

filters = filters.ToList();

作为方法的第一行,有效地复制一份。请注意,委托是不可变的,因此您无需担心他们更改。

答案 1 :(得分:1)

哦,太甜蜜了!你非常接近,但足够接近让我得到答案。要应用所有过滤器,return true需要移出foreach循环(如下所示)。但是,是的,这正是我正在寻找的。

一个问题或评论。真正让我觉得这个函数是x变量。我必须运行并调试代码,以确定x将是<T>类型。我以前从来没有见过一个没有类型或var声明的变量,这真的让我失望了。你能解释一下C#允许变量x没有任何声明的规则吗?

顺便提一下非常优雅的解决方案。

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

答案 2 :(得分:0)

为什么不一个接一个地应用这些功能,如第一个例子?

您的过滤器具有

的签名
Func<IEnumerable<ImageInfo>, IEnumerable<ImageInfo>> 

那么只需将每个过滤器应用于最后一个过滤器的结果吗?

像这样?

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

编辑重新:评论,在我的头顶,像... ...

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

答案 3 :(得分:0)

对于它的价值,你不需要使用循环。 (在Python中,因为我没有使用过C#:)

def conjoin(filter1, filter2):
    return lambda x: filter1(x) and filter2(x)

def conjoin_list(filters):
    return reduce(conjoin, filters)

(Equivalent of the reduce function in C#, called fold there.)