CodeGym /课程 /C# SELF /把 lambda 表达式当作委托类型

把 lambda 表达式当作委托类型

C# SELF
第 50 级 , 课程 0
可用

1. Introduction

想象你有台咖啡机。通常它只是煮咖啡,不会干别的,但有时候老板会说:「你能在煮完咖啡后喊一声“好了!”吗?」——这就需要配置。我们不改咖啡机的代码,只是创建一个函数,让它在最后执行。

在 C# 里,这个角色由 委托 承担 —— 它们允许把一段逻辑(方法、lambda 或匿名方法)传给另一个方法,以便在合适的时候调用。简单说,委托是一种可以保存指向具有特定签名的方法引用的类型。

Delegate definition

在 C# 中,委托用关键字 delegate 来定义。示例:

// 委托,引用接受 int 并返回 bool 的方法
public delegate bool PredicateInt(int x);

现在类型为 PredicateInt 的变量可以引用任何接受一个 int 并返回 bool 的方法(或者 lambda!)。

What are delegates for?

  • 把逻辑作为参数传递(比如排序、过滤、事件处理)
  • 订阅事件(后面会讲)
  • 实现回调(callback)
  • 设计灵活的 API,让调用方决定部分行为

Simple visual schema

委托类型 签名 调用示例
Action
void()
Action act = () => ...
Func<int>
int()
Func<int> f = () => 42
Func<int, bool>
bool(int)
Func<int, bool> p = x => x > 0

2. How a lambda turns into a delegate

Синтаксис

当你写一个 lambda,比如 x => x > 5,本质上是在创建一个委托对象。lambda 并不是孤立存在的:它必须有一个类型(某处需要知道它的参数和返回类型)。这就是为什么 C# 中的 lambda 总是被隐式或显式地转换成某个委托类型。

Example 1: Binding a delegate to a method

// 明确定义委托
public delegate bool MyPredicate(int number);

class Program
{
    static void Main()
    {
        // 把 lambda 赋给 MyPredicate 类型的变量
        MyPredicate isEven = x => x % 2 == 0;

        Console.WriteLine(isEven(4));  // true
        Console.WriteLine(isEven(7));  // false
    }
}

Example 2: Using standard delegates

C# 提供了一组通用的标准委托:ActionFunc<>Predicate<>。在写 lambda 的地方它们经常出现。

// 使用 Func
  
    Func
   
     isPositive = number => number > 0; Console.WriteLine(isPositive(-5)); // false 
   
  

3. Standard delegates: Func, Action, Predicate

Func<...>

用于那些有输入并且有返回值的方法。

签名说明:
— 最后一个类型参数是返回值,之前的都是参数类型,例如:
Func<int, string> — 接受一个 int,返回一个 string

Func
  
    intToString = number => "数字: " + number; Console.WriteLine(intToString(7)); // "数字: 7" 
  

Action<...>

用于不需要返回值的方法(void)。

Action
  
    printHello = name => Console.WriteLine("你好," + name + "!"); printHello("瓦西里"); // "你好,瓦西里!" 
  

Predicate<T>

本质上是 Func<T, bool> 的简化版。用于对对象做布尔判断(true/false)。

Predicate
  
    isOdd = x => x % 2 != 0; Console.WriteLine(isOdd(3)); // true 
  

Visual cheat sheet

委托 签名 用途
Func<T1, TResult>
Result(T1)
转换、映射
Action<T1>
void(T1)
副作用、输出
Predicate<T>
bool(T)
过滤、查找

Which delegate type to choose for a lambda expression?

  • 如果有返回值,选 Func<...>
  • 如果方法不返回值(void),用 Action<...>
  • 如果需要做布尔判断,选 Predicate<T>

Example: List filtering

List
  
    numbers = new List
   
     { 1, 2, 3, 4, 5, 6 }; // 该方法期望 Predicate
    
      List
     
       evenNumbers = numbers.FindAll(x => x % 2 == 0); Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6 
     
    
   
  

4. Useful nuances

Lambda expressions and collection methods: what's under the hood?

当你在调用集合方法时传入 lambda,例如:

var adults = users.Where(u => u.Age >= 18);

方法 Where 期望一个 Func<T, bool> 类型的参数。也就是说,你的 lambda u => u.Age >= 18 被编译器转换成这个类型的委托对象。

Flowchart: how it works


你的 lambda 表达式     -->      C# Compiler        -->     委托对象 (Func
  
   ) (u => u.Age >= 18) [类型已知] (准备好在 Where() 中调用) 
  

More details on typing: type inference

通常情况下,如果上下文足够清楚,编译器会自动推断出委托类型。例如,对 List<T>.Find 来说,会期望 Predicate<T>,编译器能从方法签名里知道参数类型。

List
  
    words = new List
   
     { "one", "two", "three" }; var result = words.Find(word => word.Length == 5); // Find 会期望 Predicate
    
      Console.WriteLine(result); // "three" 
    
   
  

如果上下文不明确,就需要帮编译器指定类型:

// 显式指定类型
Func
  
    check = x => x > 10; 
  

Returning delegates: function factory

有时候方法会返回委托 —— 也就是生成“函数工厂”。这在需要动态生成行为时很方便。

// 返回委托(lambda)的函数
Func
  
    GetMultiplier(int factor) { return x => x * factor; } var times3 = GetMultiplier(3); Console.WriteLine(times3(5)); // 15 
  

这是可行的,因为 lambda (x => x * factor) 捕获了外部上下文中的变量 factor(closure/闭包),并作为 Func<int, int> 类型的对象返回。

5. Errors and misunderstandings with delegates and lambdas

Signature mismatch

如果参数或返回类型不匹配,编译器不会允许把 lambda 赋给委托。

Func
  
    f = x => "不能返回字符串!"; // 编译错误 
  

Error when trying to use a lambda without a delegate

不能直接写一个 lambda 然后尝试调用它而不给出类型:

// 这样不行 —— 编译器无法推断类型
// var myFunc = x => x * 2; // Error CS0815 
// myFunc(10);

要让它工作,需要显式指定类型,或者提供上下文:

Func
  
    myFunc = x => x * 2; Console.WriteLine(myFunc(10)); // 20 
  

Confusion between Action, Func and Predicate

有时会选错委托类型,导致签名不匹配。记住简单规则:Func —— 有返回值时用,Action —— 没有返回值(void)时用,Predicate<T> —— 需要布尔结果(bool)时用。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION