65.9K
CodeProject 正在变化。 阅读更多。
Home

委托(Delegate、Func、Predicate、Action 和 Lambda)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (26投票s)

Nov 9, 2014

CPOL

4分钟阅读

viewsIcon

50153

对 Delegate、Func、Predicate、Action 和 Lambda 表达式的详细描述。

引言

在本文中,我将描述一些令人困惑的术语,包括 Func、Action、Predicate、lambda 表达式和 delegate,以及它们之间的区别。尽管泛型和 lambda 表达式的引入在一定程度上增加了这些术语的复杂性,但它们本质上都是一样的:委托。

这将帮助您更好地理解这些术语,并可能解决对象和集合之间复杂的交互问题。

背景

委托在编程界存在已久,但在 .Net 中,随着时间的推移,为了创造更多解决问题的方法,人们添加了许多形式的委托。

让我们从委托的简单定义开始。委托是对一段执行某事的代码的引用!就像一个方法一样。它有作为输入的参数和作为输出的结果。

delegate(int x) {

    return x == 0;
}

如果我们用 替换 delegate 关键字

public bool FunctionName

那么它将变成

public bool FunctionName(int x){
    return x == 0;
}

所以基本上,一种形式可以是一个没有名字的函数,更专业地说,是一个匿名函数。委托也可以有一个像下面这样的名字,它引用一个具有相同签名的方法。

public bool delegate Name(int x);

然后,它可以与事件关联,以便其他对象可以订阅它,并在事件触发时收到通知(这超出了本文的范围)。

既然我们知道什么是委托,让我们在一些激动人心的地方使用它,比如 LINQ 和集合。假设我们有一个像这样的集合

var collection = new List();
collection.Add(1);
collection.Add(2);
collection.Add(3);

如果我们想过滤这个集合的元素,我们通常会写

collection.Where(x => x == 2);

我会在本文稍后回到 lambda 表达式并对其进行解释。

委托

现在,如果我想用委托来描述括号内的条件呢?请记住,委托应该引用一段执行某事的代码,基本上就像一个方法。那么为什么不比较一些元素并为我们过滤它们呢,就像我之前提到的 lambda 表达式一样。那么,如果我写

delegate(int item)
{
    if(item == 2)
    {
        return true;
    }
    return false;
}

以及随后

collection.Where(delegate(int item)
            {
                if (item == 2)
                {
                    return true;
                }
                return false;
            });

怎么样?所以这只是一个委托,它接受一个整数作为输入并返回一个布尔值作为输出。

功能

委托的另一种形式是 Func。Func 可以有任意数量和类型的输入参数,并且可以返回任何类型的输出,与委托完全一样。签名类似于

Func<int, bool> condition = delegate(int item)
{
    if(item == 2)
    {
        return true;
    }
    return false;
};

因为 Func 可以返回任何类型,所以我们需要在签名中指定返回类型(在本例中为 bool,它实际上是泛型定义中的最后一个参数)。

Func<int, bool> condition = deleg...

我可以写

var result = collection.Where(condition);

谓词

如果一个 Func 只返回一个 bool,那么它将有一个不同的名称,称为 Predicate, 只是为了让我们更困惑一点。所以,谓词是一个总是返回布尔值但具有任意数量和类型输入的委托。因此:

Predicate<int> predicate = delegate(int item)
{
    if(item == 2)
    {
        return true;
    }
    return false;
};

但是,如果我们要像之前那样使用它,就需要将这个谓词转换为 Func。

Func<int, bool> condition = new Func<int, bool>(predicate);
collection.Where(condition);

但有些情况我们可以直接使用谓词。

collection.Find(predicate);

最佳实践:使用 Func、Lambda 表达式和委托而不是 Predicate。

操作

现在,如果我的 Func 没有返回值,那会是什么呢?是的!是 Action。所以,基本上 Action 是一个不返回任何值的委托。类似于

Action<int> action = delegate(int item)
{
    item += 2;
}

我可以用这个 Action 来处理之前的过滤示例吗?不太可能,因为 Where 方法的参数应该返回一个布尔值来判断该项是否应包含在结果中,对吧?那么我可以在哪里使用 Action 呢?

答案是,每当我想要执行一个不返回任何值的操作时,比如

collection.ForEach(action);

所以这会针对集合中的每个项运行,并将 2 添加到每个项中。但请注意,虽然这个 Action 是为集合中的每个项运行的,但在此情况下更改未生效,因为我们正在使用 LINQ 的 for each 循环修改集合,这是不允许的。但如果这是一个引用类型,该类型的属性可以在 for each 循环中更改,就像下面的示例一样。

Lambda 表达式

现在我想为委托定义另一个同义词,即 Lambda 表达式。Lambda 表达式可以是 Func 或 Action 的形式,因此它本身就是委托。 

所以 Where 参数的替代可以是

Predicate<int> condition = (int x) => x == 2;
Func<int, bool> condition = (int x) => x == 2;

对于 ForEach 方法,我可以写

Action<int> action = (int item) => item += 2;

就是这样。我们都弄明白了,所有的困惑都消失了。我下面为每个术语写了一些更复杂的示例。希望有所帮助。

示例

public class Customer
{
    public string Name { get; set; }
    public string Telephone { get; set; }
}
var customers = new List<Customers>();

customers.Add(new Customer() {
    Name = "cust A",
    Telephone = "123"
});
​customers.Add(new Customer() {
    Name = "cust B",
    Telephone = "456"
});
​customers.Add(new Customer() {
    Name = "cust C",
    Telephone = "789"
});

1. 委托 

customers.Where(delegate(Customer cust) {
    return cust.Name == "cust B";
});

2. Func

Func<Customer, bool> filter = delegate(Customer cust){

    return cust.Name == "cust B";
};

customers.Where(filter);

3. Predicate

Predicate<Customer> filter = delegate(Customer cust){

    return cust.Name == "cust B";
};

customers.Find(filter);

4. Lambda

Func<Customer, bool> filter = (Customer cust) => cust.Name == "cust B";

customers.Where(filter);

5. Action

We can't use Actions here because it doesn't return any value let alone bool.

现在让我们玩玩 ForEach 方法,而不是过滤数据

1. 委托

customers.ForEach(delegate(Customer cust){
    cust.Name = "cust B name Modified !!!";
});

2. Func

We can't use Func here, as Func has to always return something.

3. Predicate

We can't use predicate here as well, as it always has to return boolean.

4. Lambda

customers.ForEach(cust => cust.Name = "cust B Modified !!!");

5. Action

Action<Customer> filter = (Customer cust) => cust.Name = "cust B Modified !!!";

customers.ForEach(filter);

*) 现在一个关键问题!为什么以下代码片段会出现编译错误?

var simpleDelegate = delegate(int x) {

    return x == 0;
};

答案是,因为正如您所看到的,有不同的结构(predicate、func、delegate)可以与此委托关联,而编译器不知道选择哪一个 J。

© . All rights reserved.