C#中的func<>
2026/9/6 7:08:30 网站建设 项目流程

c# 里面的 func<> 是什么?

C# Func<>

Func<TResult>.NET 内置的泛型委托,用来封装有返回值的方法;与之对应:

  • Func:有返回值
  • Action:无返回值(void)

委托:可以把方法当成变量来传递的类型。

重载版本

Func最多支持16 个输入参数最后一个泛型参数永远是返回值类型

// 无参数,返回 TResult public delegate TResult Func<TResult>(); // 1个输入参数T1,返回TResult public delegate TResult Func<T1, TResult>(T1 arg1); // 2个输入参数 public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2); // …一直到 Func<T1,…,T16,TResult>

✅记忆:最后一个类型是返回值,前面全是入参

简单示例

1. 普通方法赋值给 Func

// Func<int, int>:输入int,返回int Func<int, int> square = CalcSquare; int res = square(5); // 调用,res=25 int CalcSquare(int x) { return x * x; }

2. 搭配 Lambda(最常用)

日常几乎都是配合 lambda 简写,不用单独写方法:

Func<int, int> square = x => x * x; Console.WriteLine(square(6)); //36 //两个入参,返回bool Func<int, int, bool> isBigger = (a, b) => a > b; Console.WriteLine(isBigger(10,2)); //True //无参数有返回值 Func<string> getMsg = () => "hello func"; Console.WriteLine(getMsg());

和 Action 的对比

表格

类型是否有返回值示例
Func<TResult>✅有返回值Func<int> f=()=>10;
Action❌void 无返回Action a=()=>Console.WriteLine(1);

常见使用场景

  1. LINQ:Where、Select 接收的就是 Func 委托
var list = new List<int>{1,2,3,4}; // Where接收 Func<int,bool> var r = list.Where(x => x>2);
  1. 回调函数、策略模式,把一段逻辑当参数传入方法
//接收一个Func做计算逻辑 static int DoWork(Func<int,int> op, int val) { return op(val); } //调用 int v = DoWork(x=>x*x,7);
  1. 延迟执行:把逻辑包装起来,后面再执行func()

容易踩坑点

  1. 最后一个泛型是返回值,不要写反
//错误!以为<int,int>是两个int入参,实际第一个int是返回值,没有入参 Func<int,int> bad; // ✔ 第一个int入参,第二个int返回 Func<int, int> ok;
  1. Func不能返回 void;要 void 就用Action
// Func<int,void> 编译报错!void不能做泛型参数 Action<int> act = x=>Console.WriteLine(x); //正确
  1. 闭包陷阱:lambda 捕获外部变量,是捕获变量本身不是当时的值。

小结

Func<...>= 可以存带返回值方法的泛型委托,最后一个泛型参数代表返回类型,广泛用于 Lambda、LINQ、回调。

如果你需要,我可以给你对比PredicateFuncAction三者区别。


相当于 delegate 的语法糖?

Func<> 是不是 delegate 的语法糖?

不完全是语法糖,它本身就是编译器预定义好的泛型 delegate 类型。

本质

public delegate TResult Func<TResult>(); public delegate TResult Func<T1,TResult>(T1 arg1);

FuncAction就是.NET 类库里面已经帮你写好的 delegate 声明,不是 C# 编译器给你做的语法转换糖。

语法糖:编译器看到简写,自动帮你生成 delegate 代码,比如=>lambda 是语法糖。 Func<>:是 BCL 已经定义好的委托类型,你直接拿来用。

对比

  1. 自己手写 delegate:
// 自己声明一个委托类型 public delegate int MyCalc(int x);
  1. 使用内置 Func,效果逻辑等价,不用自己写 delegate 声明:
Func<int,int> calc;

Func<int,int>和上面手写的MyCalc是两个完全不同的委托类型,虽然签名一模一样,但是类型不一样,不能直接互相赋值,需要转换。

MyCalc d1 = x=>x*2; Func<int,int> d2 = d1; // 编译报错,类型不同!

哪怕签名相同,不同 delegate 类型属于不同类型,不能隐式转换。这一点很关键。

理清概念分层

  1. delegate:关键字,用来声明委托类型
  2. Func<> / Action<>:BCL 预先用delegate关键字写好的一堆泛型委托类型,直接复用。
  3. Lambdax=>...:这才是语法糖,编译器把 lambda 生成匿名方法,可以赋值给匹配签名的委托(可以赋值给 Func、Action、也可以赋值给你自己手写 delegate)。

通俗总结

  • delegate:造委托类型的模具
  • Func<>:微软已经用模具造好一批现成委托类型,直接用,不用你再拿模具造
  • lambda:写委托实例的简写语法糖

Predicate 补充

Predicate<T>也是预定义委托:delegate bool Predicate<T>(T obj),等价于Func<T,bool>,但是独立类型,二者也不能直接互赋值。

小例子区分

//1.自己造委托类型 delegate int MyDel(int a); //2.使用BCL现成委托类型Func Func<int, int> funcDel; //3.lambda语法糖,赋值给任意匹配签名委托 MyDel d1 = x => x + 1; Func<int, int> d2 = x => x + 1; // d1 = d2; //编译错误!类型不一样 // d2 = d1; //编译错误!类型不一样 //只能通过lambda/匿名方法中转 Func<int,int> d3 = d1.Invoke;

关键点记忆

Func不是简化 delegate 声明的语法糖,是现成 delegate 类型;lambda 才是语法糖。 签名一样,不同委托类型仍然是不同类型,不能隐式互换。

如果你需要,我可以讲下匿名方法、lambda、Func、自定义 delegate 完整演进对比。


delegate 范围比 func 大?大在哪里

delegate 范围比 Func<> 大在哪里

一句话:delegate是 C# 关键字,用来定义任意委托类型;Func<>只是.NET 预先定义好的一小部分委托实例。delegate能做的,Func 不一定能做;Func 只是 delegate 的子集。

类比:delegate= class 关键字(用来定义类)Func<>= .NET 已经写好的某个类,你直接 new 使用。

1. Func 有硬性限制

Func 家族全部遵循:

  • 返回值不能是 void;void 交给 Action
  • 最多16 个泛型参数Func<T1…T16,TResult>
  • 返回值必须放在最后一个泛型位置

也就是说:Func 只能表达:0‑16 个参数 + 带返回值的方法签名。

delegate 没有这些限制,这就是它更大的地方

① 可以定义 ref /out 参数的委托,Func 做不到

BCL 内置的Func/Action全部不支持 ref、out。 如果你需要委托带 out/ref,只能自己delegate

// ✅自定义delegate支持 out delegate bool TryParseDel(string s, out int val); // ❌ Func<string, int, bool> 无法表达 out int,没有语法可以标记out

Func 泛型参数只能代表类型,不能修饰参数修饰符ref/out/in。这是最常用的差距。

② 可以定义返回 void,也可以自定义语义名称

Func 不能 void 返回;void 用 Action。 但自定义 delegate 可以给委托起业务含义名字:

// 语义清晰:代表事件回调 public delegate void DataChangedEventHandler(object sender, DataChangedArgs e);

Action<object, DataChangedArgs>也能装,但名字看不出业务含义。

③ 可以超过 16 个参数

Func 最多 16 个入参。 如果真的需要 17 个参数的委托,只能手写 delegate。(现实极少遇到)

④ 可以定义静态委托、绑定到实例,还能用于事件

C# 的event事件底层依赖 delegate 类型

你可以把事件声明为自定义 delegate; 虽然现在也常用EventHandler<T>(也是内置 delegate),但不能直接把 event 声明成 Func

// 合法 public event DataChangedEventHandler OnDataChange; // ❌编译报错,event不能用Func<> public event Func<int> OnDataChange;

事件语法要求委托类型,Func 是委托类型,但C# 语法禁止 event 使用 Func/Action,因为事件设计初衷是多播回调,Func 有返回值会产生歧义(多个返回值取哪个?)。

⑤ 可以定义泛型约束,Func 是固定写死的

自定义 delegate 可以加泛型约束:

delegate T CreateDel<T>() where T : new();

内置 Func 是 BCL 已经写死,你不能修改它的泛型约束。

范围关系图

delegate关键字(可以定义任意委托类型) ├─ 自己手写 delegate MyDel(...) 【完全自由:ref/out、命名、约束、事件】 └─ .NET内置预定义委托 ├─ Func<T…> :0‑16参数,有返回值,不能ref/out ├─ Action<T…> :0‑16参数,void返回,不能ref/out └─ Predicate<T>、EventHandler<T> 等

关键误区纠正

“Func 是 delegate” —— 这句话对:Func 本质就是用 delegate 关键字声明出来的类型。 “delegate 就是 Func” —— 错:delegate 是定义工具,Func 是工具产出的其中一个成品。

什么时候用哪个?

  1. 普通回调、LINQ、lambda:优先Func/Action,省事。
  2. 需要ref/out参数:必须手写 delegate
  3. 写事件:优先EventHandler<T>,不要 Func/Action。
  4. 需要业务语义名称、泛型约束:自定义 delegate。

补充小坑

即便签名完全一致:

delegate int MyDel(int x); Func<int, int> f;

MyDelFunc<int,int>是两个完全不同的委托类型,不能隐式转换,只能通过 lambda 或者.Invoke中转。

如果你需要,我可以举个 ref 参数委托完整可运行示例。


func 是委托的语法糖?

结论先说

Func<> 不是委托的语法糖。

  • delegate是关键字,用来定义委托类型
  • Func<T1,TResult>.NET BCL 已经用 delegate 定义好的委托类型,是一个实实在在的类型,不是编译器帮你偷偷生成的简写。
  • Lambdax=>x+1才是语法糖

拆开看

这个是源码级别的定义,.NET 内部就是这么写的:

public delegate TResult Func<TResult>(); public delegate TResult Func<T1, TResult>(T1 arg1);

这一行是类库代码,不是编译器看到你写Func<>之后临时生成出来的。

什么叫语法糖?

语法糖:你写简短写法,C# 编译器在编译阶段自动帮你展开成等价代码,源码里不存在那个简写对应的类型。

举例:lambda 就是语法糖:

Func<int,int> f = x => x * 2;

编译器会把这个 lambda 翻译成匿名方法,再赋值给委托对象。这是糖。

Func<int,int>本身不是糖,它就是一个已经存在的委托类型,就跟你自己写:

public delegate int MyDelegate(int x);

产出的MyDelegate地位完全一样:都是委托类型,只是一个是微软写好放库里面,一个是你自己写。


很多人会混淆的点

“用 Func 就不用手写 delegate 了,这不就是语法糖吗?”

不是语法糖,这只是复用现成类型,省去你手动声明委托类型的工作量

关键证据:

  1. 相同签名,不同委托类型不能隐式转换
delegate int MyDel(int x); MyDel d1 = x=>1; Func<int,int> d2 = d1; // ❌编译报错,类型不一样

如果 Func 只是语法糖,编译器应该把二者视为同一类型,但实际不是。它们是两个独立的委托类型。

  1. Func 有硬边界:不支持ref/out/in参数,最多 16 个参数。 如果它是编译器语法糖,理论上编译器可以帮你生成支持 ref/out 的版本,但它做不到,因为类型是库里面写死的。

层级关系梳理

  1. delegate关键字:制造委托类型的工具
  2. Func/Action:工具已经造好的一批现成委托类型(BCL 内置)
  3. Lambda=>语法糖,编译器生成匿名方法,可赋值给任意匹配签名的委托类型(自定义 delegate、Func、Action 都可以接)

类比:

  • delegate好比struct关键字
  • DateTime是 .NET 内置的 struct,你直接用
  • 你不能说 DateTime 是 struct 的语法糖,它只是一个现成结构体。同理 Func 不是 delegate 的语法糖。

一句话区分记忆

  • ✅ Lambda=>语法糖
  • ✅ Func<>:预定义的委托类型,不是语法糖
  • ✅ delegate:定义委托类型的关键字

如果你愿意,我可以再对比匿名方法、lambda、Func、自定义 delegate,看一段代码编译后 IL,直观看出差异。


就是 func 可以系统自己识别你的委托类型,而 delegate 要自己设置

你的这个感受非常贴近日常写代码的直觉,但表述要修正一下:

❌不是系统 “识别你的委托类型” ✅是Func<> 是已经存在的现成委托类型,你直接填泛型参数就能用;delegate 关键字是让你全新定义一个委托类型

对比举例

用 delegate:你要先造类型,再用

// 第一步:手动定义一个全新的委托类型 MyCalc delegate int MyCalc(int x); // 第二步:再去声明变量 MyCalc fn = x => x * 2;

MyCalc是你创造出来的新类型,程序集里会多出这个类型。

用 Func<>:类型早就有了,你只需要填类型参数

// 没有定义新类型!Func<int,int>这个类型本来就在BCL里 Func<int, int> fn = x => x * 2;

你并没有创造新委托类型,只是实例化那个已经存在的泛型委托。


关键误区:它不会自动识别你的方法然后生成委托

很多人以为:我写个 lambda,编译器看我入参返回,自动生成 Func。 不是。是你显式写了Func<int,int>,编译器才知道要把 lambda 赋值给这个已有的委托类型。

// 这行单独写,编译报错!编译器猜不出来你要什么委托类型 var fn = x => x * 2;

var fn = x=>x*2;直接报错,lambda 本身没有类型,必须赋值给一个明确的委托类型,可以是:

  • 现成的Func<int,int>
  • 或者你自己delegate定义的MyCalc

lambda 本身是 “逻辑片段”,没有自身类型,必须挂靠到某一个委托类型上。


通俗大白话总结你的理解

你的原话:func 可以系统自己识别你的委托类型,而 delegate 要自己设置

修正版:

Func 是系统预先写好一批委托类型,你只要填泛型参数就拿来用,不用自己新建委托类型; delegate 关键字是用来从零新建一个属于你自己的委托类型。 两者都需要显式指定类型,Func 不会自动推断生成委托。

核心差异点

  1. delegate A(...)定义新委托类型,输出一个新类型到程序集
  2. Func<TIn,TOut>使用已经存在的委托类型,不产生新类型
  3. Lambda:无类型,只能赋值给上面两种委托类型之一

再强调那个坑

哪怕签名一模一样:

delegate int MyCalc(int x); MyCalc a = x=>x+1; Func<int,int> b = a; // 编译报错!类型不一样,不能直接赋值

虽然方法签名完全匹配,但MyCalcFunc<int,int>是两个完全不同的类型,不能隐式转换。 如果 Func 是 “自动识别生成”,那这行就应该能通过,但现实不行。


什么时候选哪个

  • 普通回调、LINQ:直接 Func / Action,不用自己定义委托类型,省事。
  • 需要 out/ref 参数、写事件、想要业务类型名字:必须自己delegate定义新委托类型。

如果你想看,我可以简单讲下 IL 层面,看自定义 delegate 会多出一个类型,而 Func 不会。


这不是一样吗你对比举例的两个

表面写出来的代码看着效果行为一样:都能存一个int→int的方法,调用起来结果一样。 但是类型层面完全不一样,这就是很多人迷惑的根源。

看两段代码:

//版本A:自己delegate delegate int MyCalc(int x); MyCalc fnA = x => x * 2; int resA = fnA(5); //版本B:用Func Func<int, int> fnB = x => x * 2; int resB = fnB(5);

运行结果:resA=10,resB=10执行效果一模一样

所以你感觉:这不是一样吗? ✔运行行为一样但是类型、编译产物、能否互相赋值完全不一样


核心差别:编译之后程序集里面发生了什么

A 方案(自定义 delegate)

delegate int MyCalc(int x);

编译器会在你的 dll/exe 里生成一个全新的类型:MyCalc,继承自MulticastDelegate。 这是属于你项目的新类型。

B 方案(Func<int,int>)

Func<int,int>不是你项目生成的,它来自系统程序集System.Private.CoreLib.dll,早就写死在框架里。 你只是引用这个现成类型,你的程序集不会多出任何新委托类型

👉 一个是 “你造一个新盒子”;一个是 “拿系统现成的盒子”。两个盒子外形尺寸一模一样(装的方法签名一样),但盒子本身不是同一个东西。

证据:不能互相赋值

MyCalc fnA = x => x * 2; Func<int, int> fnB = fnA; // ❌编译报错!

盒子长得一样,但是商标不一样,不能直接把 A 盒子的东西交给 B 盒子变量。

如果它们真的 “完全一样”,上面这行就应该编译通过,实际不行。


那为什么 lambda 又可以同时赋值给两者?

MyCalc fnA = x=>x*2; Func<int,int> fnB = x=>x*2;

lambda 本身只是一段逻辑,编译器根据左边变量的委托类型,帮你包装成对应委托对象

  • 左边是MyCalc→ 包装成MyCalc实例
  • 左边是Func<int,int>→ 包装成Func<int,int>实例

lambda 不是生成 Func,它适配左边是什么委托,就生成什么委托实例。


打个生活化比喻

  • delegate int MyCalc(int x):你自己动手定做一个盒子,规格:输入 int,输出 int,这个盒子归你的项目。
  • Func<int,int>:商店(.NET 框架)已经量产好一模一样规格的盒子,你直接买回来用。

两个盒子装东西的规则完全相同,放进去 5,出来 10。 但是:

  1. 来源不一样:一个你造,一个系统造
  2. 类型身份不一样:不能把定做盒子直接交给商店盒子的变量接收
  3. 定做盒子你可以改规格:比如支持out/ref;商店量产的 Func 盒子规格固定死改不了。

总结你的困惑

“代码写出来效果一样,那到底差别在哪?”

  • 运行调用逻辑:相同
  • CLR 类型身份:不同
  • 编译输出:自定义 delegate 会新增类型;Func 复用系统已有类型
  • 能力边界:自定义 delegate 可以 ref/out;Func 做不到

日常业务写回调,绝大多数场景你感知不到差异,所以很多人误以为 Func 就是 delegate 的简写语法糖。但底层类型模型上二者不能等同。

如果你想看,我可以用伪 IL 展示两者编译后的差别。


func 只能定义 int 类型盒子?

不是,Func<>盒子不是只能 int,泛型可以填任意类型。

//输入string,返回bool Func<string, bool> f1 = s => s.Length > 0; //无参数,返回List<int> Func<List<int>> f2 = () => new List<int>(); //两个输入:string,int,返回DateTime Func<string, int, DateTime> f3 = (str, num) => DateTime.Now;

盒子的入参、返回值全部由你填的泛型参数决定,string、class、自定义类、结构体都可以。


但是!盒子的结构是固定死的(重点)

Func 这个 “盒子模具” 是微软写死的:

Func[T1,T2,...,TResult]

  • 前面全部是输入参数的类型
  • 最后一个永远是返回值类型
  • 只能写类型,不能加修饰符:ref /out/in

✅可以随便换什么类型 ❌不能修改参数的修饰符,这是模具本身的限制。

举例子,Func 做不到的

我想要一个委托:接收out int参数

// 设想,但是 C# 不支持!泛型占位符不能标记 out Func<string, out int, bool> // ❌语法错误

泛型参数只代表 “是什么类型”,不能写out/ref这种场景,就必须自己用 delegate 造新盒子

delegate bool MyTryParse(string s, out int value);

这个自己造的盒子,我可以自由加 out/ref/in,这是 Func 量产盒子做不到的。


再回顾比喻

  • Func<>:商店量产的一批盒子。
    • 盒子的内部卡槽结构固定:若干输入槽,一个输出槽;卡槽只能放类型,不支持 ref/out 卡扣。
    • 但是卡槽里放什么类型(int、string、自定义类)由你自己选。
  • 手写delegate:你自己开模做盒子。
    • 卡槽数量你定,还可以额外加上 ref/out 卡扣。卡槽放什么类型也你定。

Func 限制的不是里面放什么数据类型,而是盒子本身的参数修饰语法结构

再看一个自定义类示例,Func 完全能用

public class User { public string Name; } //输入User对象,返回string Func<User, string> getName = u => u.Name;

没问题,随便用自定义 class。

Func 还有一个硬限制

最多16 个输入参数Func<T1,T2…T16,TResult>,超过 16 个入参,Func 这套模具就没有了,只能手写 delegate。


极简小结

  1. Func 泛型可以填任意数据类型,不限于 int。
  2. 限制不在类型,在于参数修饰符 ref/out/in 不支持、最多 16 入参
  3. 一旦需要 ref/out,只能 delegate 自己造盒子。

delegate 和 func 分别支持的数据类型帮我归纳下

delegate vs Func<> 支持的数据 / 参数能力归纳

注意:这里说的不是能传什么数据类型(int/string/class 都支持),而是参数修饰符、参数数量、返回值形式,普通数据类型两者全都支持。

1️⃣ Func<>(系统预定义委托)

底层:public delegate TResult Func<T1,T2,...,TResult>(T1 arg1, T2 arg2…)

表格

项目Func<>
普通值类型✅ int、bool、DateTime、struct 全部支持
引用类型✅ string、自定义 class、List<T> 全部支持
返回值必须有返回值,不能 void;void 请用 Action
参数数量✅ 0~16 个输入参数;超过 16 个❌不支持
ref参数完全不支持
out参数完全不支持
in参数完全不支持
泛型约束❌不能自定义约束,框架已经写死
用于 event 事件❌语法禁止使用 Func 做事件类型
自定义委托名称❌不能起业务语义名字,固定叫 Func

关键点:Func 的泛型占位符只能描述是什么类型,无法携带 ref/out/in 修饰符。

Func<string, int, bool> ok; //正常 Func<string, out int, bool> error; //语法报错,不允许写out

2️⃣ delegate 关键字(自定义委托)

delegate 是用来定义新委托类型的关键字,你自己写签名。

表格

项目自定义 delegate
普通值类型✅ int、bool、struct 全部支持
引用类型✅ string、自定义 class 全部支持
返回值✅ 可以返回任意类型,也可以返回 void
参数数量✅ 理论无上限,写多少个参数都行
ref参数✅ 原生支持
out参数✅ 原生支持
in参数✅ 原生支持
泛型约束✅ 可以写where T:xxx泛型约束
用于 event 事件✅ 完全支持,事件标准用法
自定义委托名称✅ 可以给委托起业务名字,例如TryParseHandler

示例:

// out、ref、void、泛型约束全都可以 public delegate bool TryHandler<T>(string input, out T result) where T : struct;

3️⃣ 一张表看清哪些场景该选谁

表格

场景选 Func/Action必须手写 delegate
普通回调、LINQ,无 ref/out
回调需要 out /ref/in 参数
写事件 (event)✅(优先 EventHandler<T>)
入参超过 16 个
想要委托带业务语义名称
需要自定义泛型约束

4️⃣容易混淆的点澄清

  1. int、string、自定义类,两种都可以用,没有区别限制不在于你传什么数据,而在于参数的修饰符
  2. Func 本质上也是 delegate,是微软用 delegate 关键字定义好的一批委托;只是它的签名模板固定死了,不支持 ref/out。
  3. Action:Func 的兄弟,无返回值,同样不支持 ref/out/in。

极简记忆口诀

Func/Action:类型随便填,ref/out 不能玩;最多 16 参数,事件不能干。 自定义 delegate:签名我做主,ref/out 随便撸;事件约束泛型,全部都能铺。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询