C#中的Action<T>、Func<T>委托和Lambda表达式

本文介绍了.NET中Action和Func泛型委托的应用场景与Lambda表达式的使用方法。详细讲解了不同参数数量的Action与Func委托,并通过示例展示了Lambda表达式在实际编程中的简洁性与灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用Action、Func和Lambda表达式
在.NET在,我们经常使用委托,委托的作用不必多说,在.NET 2.0之前,我们在使用委托之前,得自定义一个委托类型,再使用这个自定义的委托类型定义一个委托字段或变量。.NET 2.0给我们带来了Action、Func两个泛型委托,.NET3.0给我们带来了Lambda,这一切使得委托的定义和使用变得简单起来。下面的例子中的委托都使用了Lambda表达式。
一.Action系列的泛型委托
Action系列的委托定义的是没有返回值(返回值为void)的委托。它有多个版本包括没有输入参数,1个输入参数,2个输入参数,3个输入参数,4个输入参数共5个版本这几个版本的原型如下:
1.       没有输入参数返回值为void的委托.
Action委托 封装一个方法,该方法不采用参数并且不返回值。
可以使用此委托以参数形式传递一个执行某操作的方法,而不用显式声明一个自定义的委托来封装此方法。该封装的方法必须与此委托定义的方法签名相对应。这意味着该方法不得具有参数和返回值。例:
using System;
using System.Windows.Forms;
public class Name
{
   private string instanceName;
   public Action ShowName;
   public Show()
{
   If(ShowName != null)
    ShowName();
}
   public Name(string name)
   {
      this.instanceName = name;
   }
   public void DisplayToConsole()
   {
      Console.WriteLine(this.instanceName);
   }
   public void DisplayToWindow()
   {
      MessageBox.Show(this.instanceName);
   }
}
public class ActionStudy
{
   public static void Main()
   {
      Name testName = new Name("Koani");
      testName.ShowName  = () => testName.DisplayToWindow();
      testName.Show();
   }
}
2.       有1个输入参数返回值为void的委托
Action泛型委托封装一个方法,该方法只采用一个参数并且不返回值。
可以使用此委托以参数形式传递方法,而不用显式声明自定义的委托。该方法必须与此
委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且不能返回值。例:
using System;
using System.Windows.Forms;
public class ActionStudy
{
   public static void Main()
   {
      Action messageTarget;
      if (Environment.GetCommandLineArgs().Length > 1)
         messageTarget = s => MessageBox.Show(s);
      else
         messageTarget = s => Console.WriteLine(s);
      messageTarget("Hello, World!");
   }
}
下面的示例演示如何使用 Action(T) 委托来打印 List(T) 对象的内容。在此示例中,使用 Print 方法将列表的内容显示到控制台上。此外,C# 示例还演示如何使用匿名方法将内容显示到控制台上。
using System;
using System.Collections.Generic;
class Program
{
    static void Main()
    {
        Action PrintInConsole = s => Console.WriteLine(s);
        Action PrintInDialog = s=>MessageBox.Show(s);
        List names = new List();
        names.Add("Bruce");
        names.Add("Alfred");
        names.Add("Tim");
        names.Add("Richard");
        names.ForEach(PrintInConsole);
        names.ForEach(PrintInDialog);     
    }
}
3.       有2个输入参数返回值为void的委托
Action 封装一个方法,该方法具有两个参数并且不返回值。
可以使用 Action(T1, T2) 委托以参数形式传递方法,而不用显式声明自定义的委托。该
方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有两个均通过值传递给它的参数,并且不能返回值。
using System;
using System.IO;
public class ActinStudy
{
   public static void Main()
  {
      string message1 = "The first line of a message.";
      string message2 = "The second line of a message.";
      Action  concat;
      if (Environment.GetCommandLineArgs().Length > 1)
         concat = (s1, s2) =>
{
StreamWriter writer = null;
      try
      {
         writer = new StreamWriter(Environment.GetCommandLineArgs()[1], false);
         writer.WriteLine("{0}"n{1}", s1, s2);
      }
      catch
      {
         Console.WriteLine("File write operation failed...");
      }
      finally
      {
         if (writer != null) writer.Close();
      }
};
      else
         concat = (s1, s2) => Console.WriteLine("{0}"n{1}", s1, s2);
      concat(message1, message2);
   }
4.       有3个输入参数返回值为void的委托
Action委托,封装一个方法,该方法采用三个参数并且不返回值。
可以使用 Action(T1, T2, T3) 委托以参数形式传递方法,而不用显式声明自定义的委托。
该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有三个均通过值传递给它的参数,并且不能返回值。
5.       有4个输入参数返回值为void的委托
Action委托, 封装一个方法,该方法具有四个参数并且不返回值。
可以使用 Action(T1, T2, T3, T4) 委托以参数形式传递方法,而不用显式声明自定义的委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有四个均通过值传递给它的参数,并且不能返回值。
二.Func系统的泛型委托
Func系列的委托定义的是返回值的委托。它有多个版本包括没有输入参数,1个输入参数,2个输入参数,3个输入参数,4个输入参数共5个版本这几个版本的原型如下:
1.       没有输入参数有返回值(返回值不为void)的委托
Func封装一个不具有参数但却返回 TResult 参数指定的类型值的方法。
可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该
方法必须与此委托定义的方法签名相对应。这意味着封装的方法不得具有参数,但必须返回值。
2.       具有一个输入参数有返回值(返回值不为void)的委托
   Func封装一个具有一个参数并返回 TResult 参数指定的类型值的方法。
可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且必须返回值。
3.       具有二个输入参数有返回值(返回值不为void)的委托
  Func封装一个具有一个参数并返回 TResult 参数指定的类型值的方法。
可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有两个均通过值传递给它的参数,并且必须返回值
4.       具有三个输入参数有返回值(返回值不为void)的委托
   Func封装一个具有三个参数并返回 TResult 参数指定的类型值的方法。
可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有三个均通过值传递给它的参数,并且必须返回值。
5.       具有四个输入参数有返回值(返回值不为void)的委托
 Func封装一个具有四个参数并返回 TResult 参数指定的类型值的方法。
可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有四个均通过值传递给它的参数,并且必须返回值。
 
三,Lambda表达式
  C#2.0介绍了一个新特性--匿名方法,允许研发者在线(inline)声明自己的函数代码而无须使用委托函数(delegate function)。C#3.0中提供了一个新特性--Lambda表达式,他提供了完成相同目标的更加简洁的格式。让我们在讨论Lambda表达式以前仔细研究一下匿名方法。 

  匿名方法

  假设你需要创建一个按钮,当点击他的时候更新ListBox里的内容。在C#1.0和1.1里,你要这样做:

public MyForm()
{
 listBox = new ListBox(...);
 textBox = new TextBox(...);
 addButton = new Button(...);
 addButton.Click += new EventHandler(AddClick);
}

void AddClick(object sender, EventArgs e)
{
 listBox.Items.Add(textBox.Text);
}

  在C#2.0里,你需要这样做:

public MyForm()
{
 listBox = new ListBox(...);
 textBox = new TextBox(...);
 addButton = new Button(...);
 addButton.Click += delegate
 {
  listBox.Items.Add(textBox.Text);
};

  就像你看到的相同,你不必要特别的声明一个新方法来将他连接到一个事件上。你能在C#2.0里使用匿名方法来完成同样的工作。C#3.0里介绍了一种更加简单的格式,Lambda表达式,你能直接使用"=>"来书写你的表达式列表,后面跟上一个表达式或语句块。

  Lambda表达式中的参数

  Lambda表达式中的参数能是显式或隐式类型的。在一个显式类型参数列表里,每个表达式的类型是显式指定的。在一个隐式类型参数列表里,类型是通过上下文推断出来的:

(int x) => x + 1 // 显式类型参数
(y,z) => return y * z; // 隐式类型参数

  Lambda演算实例

  下面的例子给出了两种不同的方法来打印出一个list中长度为偶数的字符串。第一种方法AnonMethod使用了匿名方法,第二种LambdaExample则是通过Lambda演算实现:

// Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;

namespace LambdaExample
{
 public delegate bool KeyValueFilter(K key, V value);
 static class Program
 {

  static void Main(string[] args)
  {
   List list = new List();

   list.Add("AA");
   list.Add("ABC");
   list.Add("DEFG");
   list.Add("XYZ");
   Console.WriteLine("Through Anonymous method");
   AnonMethod(list);
   Console.WriteLine("Through Lambda expression");
   LambdaExample(list);

   Dictionary varClothes= new Dictionary();

   varClothes.Add("Jeans", 20);
   varClothes.Add("Shirts", 15);
   varClothes.Add("Pajamas", 9);
   varClothes.Add("Shoes", 9);
   var ClothesListShortage = varClothes.FilterBy((string name,
   int count) => name == "Shoes" && count < 10);
   // example of multiple parameters
   if(ClothesListShortage.Count > 0)
    Console.WriteLine("We are short of shoes");
   Console.ReadLine();
 }

 static void AnonMethod(List list)
 {
  List evenNumbers = list.FindAll(delegate(string i)
  { return (i.Length % 2) == 0; });
  foreach (string evenNumber in evenNumbers)
  {
   Console.WriteLine(evenNumber);
  }
 }

 static void LambdaExample(List list)
 {
  var evenNumbers = list.FindAll(i =>(i.Length % 2) == 0); // example of single parameter
  foreach(string i in evenNumbers)
  {
   Console.WriteLine(i);
  }
 }
}

public static class Extensions
{
 public static Dictionary FilterBy
(this Dictionary items, KeyValueFilter filter)
 {
  var result = new Dictionary();
  foreach(KeyValuePair element in items)
  {
   if (filter(element.Key, element.Value))
    result.Add(element.Key, element.Value);
  }
  return result;
 }
 
}
}

  如果你安装了Visual Studio 2005 and LinQ Preview,你能使用编辑器来编译程式。如果没有的话,能使用命令行方式:

C:\Program Files\LINQ Preview\Bin\Csc.exe
/reference:"C:\Program Files\LINQ Preview\Bin\System.Data.DLinq.dll"
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Data.dll
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll
/reference:"C:\Program Files\LINQ Preview\Bin\System.Query.dll"
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll
/reference:"C:\Program Files\LINQ Preview\Bin\System.Xml.XLinq.dll"
/target:exe Program.cs

  中间语言结果显示

  打开ILDASM我们来查看一下程式,你将看到如图1所示的内容:


图1

  双击AnonMethod函数你将看到C#编译器产生的中间语言代码:

.method private hidebysig static void AnonMethod(class
[mscorlib]System.Collections.Generic.List`1 list)
cil managed
{
 // Code size 96 (0x60)
 .maxstack 4
 .locals init ([0] class [mscorlib]System.Collections.Generic.List
 `1 evenNumbers,
 [1] string evenNumber,
 [2] valuetype [mscorlib]System.Collections.Generic.List
 `1/Enumerator CSCODE_REPLACEMENT 000,
 [3] bool CSCODE_REPLACEMENT 001)
 IL_0000: nop
 IL_0001: ldarg.0
 IL_0002: ldsfld class [mscorlib]System.Predicate
 `1 LambdaExample.Program::
 `<>9__CachedAnonymousMethodDelegate1
 IL_0007: brtrue.s IL_001c
 IL_0009: ldnull
 IL_000a: ldftn bool LambdaExample.Program::
 `b__0(string)
 IL_0010: newobj instance void class [mscorlib]System.Predicate
 `1::.ctor(object, native int)
 IL_0015: stsfld class [mscorlib]System.Predicate`1
 LambdaExample.Program::
 `<>9__CachedAnonymousMethodDelegate1
 IL_001a: br.s IL_001c
 IL_001c: ldsfld class [mscorlib]System.Predicate`1
 LambdaExample.Program::<>
 9__CachedAnonymousMethodDelegate1
 IL_0021: callvirt instance class [mscorlib]System.Collections.
 Generic.List`1 class [mscorlib]System.
 Collections.Generic.List`1::
 FindAll(class [mscorlib]System.Predicate`1)
 IL_0026: stloc.0
 IL_0027: nop
 IL_0028: ldloc.0
 IL_0029: callvirt instance valuetype [mscorlib]System.Collections.
 Generic.List`1/Enumerator class
 [mscorlib]System.Collections.Generic.List`1
 ::GetEnumerator()
 IL_002e: stloc.2
 .try
 {
  IL_002f: br.s IL_0042
  IL_0031: ldloca.s CSCODE_REPLACEMENT 000
  IL_0033: call instance !0 valuetype [mscorlib]System.
  Collections.Generic.List`1/Enumerator
  ::get_Current()
  IL_0038: stloc.1
  IL_0039: nop
  IL_003a: ldloc.1
  IL_003b: call void [mscorlib]System.Console::
  WriteLine(string)
  IL_0040: nop
  IL_0041: nop
  IL_0042: ldloca.s CSCODE_REPLACEMENT 000
  IL_0044: call instance bool valuetype [mscorlib]System.
  Collections.Generic.List`1/Enumerator
  ::MoveNext()
  IL_0049: stloc.3
  IL_004a: ldloc.3
  IL_004b: brtrue.s IL_0031
  IL_004d: leave.s IL_005e
 } // end .try
 finally
 {
  IL_004f: ldloca.s CSCODE_REPLACEMENT 000
  IL_0051: constrained. valuetype [mscorlib]System.Collections.
  Generic.List`1/Enumerator
  IL_0057: callvirt instance void [mscorlib]System.
  IDisposable::Dispose()
  IL_005c: nop
  IL_005d: endfinally
 } // end handler
 IL_005e: nop
 IL_005f: ret
} // end of method Program::AnonMethod

  这里我们能看到,实际上匿名方法和lambda表达式生成了相同的中间代码,并且他们的执行也是类似的。

  多参数的Lambda表达式

  Lambda表达式能带上多个参数,比如你能声明一个Dictionary类型:

Clothing TypeCount
Shirts15
Jeans12
Shoes9
Pajamas9

  如果你有一个匿名方法(FilterBy)来通过键和值来过滤字典,按么你能传递多个参数给lambda表达式来调用这个匿名方法。附带的代码完成了这个FilterBy的功能:

var ClothesListShortage = clothesList.FilterBy((string name, int count)
=> name == "Shoes" && count < 10);
 
 
<think>好的,我现在需要解决用户在C#中遇到的异步Lambda表达式编译错误:“无法将异步 lambda 表达式转换为委托类型 Func”。首先,我需要回忆一下C#中异步编程Lambda表达式相关的知识。 根据用户提供的引用[2],里面提到可以使用async修饰Lambda表达式,并给出了一个例子。那个例子中,Lambda表达式被赋值给一个Func<Task<HttpResponseMessage>>类型的委托变量。这说明当Lambda是异步的时候,返回类型应该是Task或者Task<T>。可能用户的问题在于Lambda表达式返回的类型与委托类型不匹配。 然后,用户提到的错误信息是“无法将异步 lambda 表达式转换为委托类型 Func”。Func委托通常表示有返回值的方法,而异步方法返回的是Task或Task<T>。如果用户试图将异步Lambda赋值给一个非Task返回类型的Func委托,比如Func<int>,而Lambda是async修饰的,那么返回类型应该是Task<int>,这时候需要Func<Task<int>>,而不是Func<int>。所以可能的错误原因是委托类型声明不正确,没有包含Task。 接下来,我要检查用户可能的代码情况。例如,假设用户写的是类似这样的代码: Func<int> func = async () => { await Task.Delay(100); return 42; }; 这里,委托类型是Func<int>,而异步Lambda实际上返回的是Task<int>,所以类型不匹配,导致编译错误。正确的应该是Func<Task<int>>。 另外,引用[4]中提到Func泛型委托的最后一个类型参数是返回值类型。所以,如果异步方法返回Task<T>,那么Func委托的类型应该是Func<Task<T>>。例如,如果Lambda返回int,那么Func应该是Func<Task<int>>。 因此,解决方法是调整委托的返回类型,使其匹配异步Lambda返回的Task类型。同时,可能需要检查是否有遗漏的async关键字,或者是否在Lambda表达式内部正确使用了await。 另外,用户可能混淆了ActionFunc。如果异步Lambda没有返回值,应该使用Action委托,但因为async方法即使没有return,也会返回Task,所以必须使用Func<Task>。例如,Action action = async () => { await ... }; 是错误的,应该改为Func<Task> func = async () => { await ... };。 总结可能的解决方案步骤: 1. 确认异步Lambda的返回类型是否为Task或Task<T>。 2. 修改委托类型为对应的Func<Task>或Func<Task<T>>。 3. 检查Lambda是否正确地使用asyncawait。 4. 确保没有混淆ActionFunc,特别是当无返回值时,应使用Func<Task>而不是Action。 此外,引用[4]提到Func委托需要有返回参数,而Action没有。因此,如果用户原本使用Action委托来接收异步Lambda,这会导致错误,因为异步方法即使没有返回值,实际上也返回Task。这时候应该用Func<Task>代替Action。 例如,错误代码可能是: Action myAction = async () => { await Task.Delay(100); }; 正确应该是: Func<Task> myFunc = async () => { await Task.Delay(100); }; 然后调用时可能需要使用await myFunc()。 所以,用户需要检查他们的委托类型是否正确,是否匹配异步Lambda返回的Task类型。</think>在C#中使用异步Lambda表达式时,若出现编译错误**"无法将异步 lambda 表达式转换为委托类型 Func"**,通常由以下原因导致: --- ### 错误原因及解决方案 #### 1. **委托类型未匹配异步返回类型** 异步Lambda表达式(使用`async`修饰)的返回类型始终是`Task`或`Task<T>`。若尝试将其赋值给非`Task`类型的`Func`委托,编译器会因类型不匹配报错。 **错误示例:** ```csharp // 错误:Lambda返回Task<int>,但委托类型是Func<int> Func<int> getNumber = async () => { await Task.Delay(100); return 42; }; ``` **修正方法:** 将委托类型改为`Func<Task<T>>`: ```csharp Func<Task<int>> getNumberAsync = async () => { await Task.Delay(100); return 42; }; ``` --- #### 2. **混淆`Action``Func`委托** 若异步Lambda没有返回值,应使用`Func<Task>`(返回`Task`),而非`Action`(返回`void`)。 **错误示例:** ```csharp // 错误:Action委托无法接收返回Task的异步Lambda Action myAction = async () => { await Task.Delay(100); Console.WriteLine("Done"); }; ``` **修正方法:** 改用`Func<Task>`: ```csharp Func<Task> myActionAsync = async () => { await Task.Delay(100); Console.WriteLine("Done"); }; ``` --- #### 3. **未正确使用`async`/`await`** 若Lambda未包含`await`,则无需标记为`async`,否则会强制返回`Task`,导致类型不匹配。 **错误示例:** ```csharp // 错误:Lambda标记为async但未使用await Func<Task<int>> getValue = async () => 42; // 实际返回Task<int> ``` **修正方法:** 移除`async`或显式返回`Task`: ```csharp // 正确方式1:移除async Func<int> getValue = () => 42; // 正确方式2:显式返回Task Func<Task<int>> getValueAsync = async () => { await Task.Yield(); return 42; }; ``` --- ### 总结步骤 1. **检查委托类型**:确保委托类型为`Func<Task>`或`Func<Task<T>>`。 2. **验证`async`必要性**:若Lambda不包含异步操作,避免使用`async`。 3. **替换`Action`为`Func`**:当异步Lambda无返回值时,使用`Func<Task>`而非`Action`。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值