C#中的func<> 📅 发布时间:2026/9/6 7:08:40 👁 浏览次数: c# 里面的 func 是什么?C# FuncFuncTResult是.NET 内置的泛型委托用来封装有返回值的方法与之对应Func有返回值Action无返回值void委托可以把方法当成变量来传递的类型。重载版本Func最多支持16 个输入参数最后一个泛型参数永远是返回值类型。// 无参数返回 TResult public delegate TResult FuncTResult(); // 1个输入参数T1返回TResult public delegate TResult FuncT1, TResult(T1 arg1); // 2个输入参数 public delegate TResult FuncT1, T2, TResult(T1 arg1, T2 arg2); // …一直到 FuncT1,…,T16,TResult✅记忆最后一个类型是返回值前面全是入参。简单示例1. 普通方法赋值给 Func// Funcint, int输入int返回int Funcint, int square CalcSquare; int res square(5); // 调用res25 int CalcSquare(int x) { return x * x; }2. 搭配 Lambda最常用日常几乎都是配合 lambda 简写不用单独写方法Funcint, int square x x * x; Console.WriteLine(square(6)); //36 //两个入参返回bool Funcint, int, bool isBigger (a, b) a b; Console.WriteLine(isBigger(10,2)); //True //无参数有返回值 Funcstring getMsg () hello func; Console.WriteLine(getMsg());和 Action 的对比表格类型是否有返回值示例FuncTResult✅有返回值Funcint f()10;Action❌void 无返回Action a()Console.WriteLine(1);常见使用场景LINQWhere、Select 接收的就是 Func 委托var list new Listint{1,2,3,4}; // Where接收 Funcint,bool var r list.Where(x x2);回调函数、策略模式把一段逻辑当参数传入方法//接收一个Func做计算逻辑 static int DoWork(Funcint,int op, int val) { return op(val); } //调用 int v DoWork(xx*x,7);延迟执行把逻辑包装起来后面再执行func()容易踩坑点最后一个泛型是返回值不要写反//错误以为int,int是两个int入参实际第一个int是返回值没有入参 Funcint,int bad; // ✔ 第一个int入参第二个int返回 Funcint, int ok;Func不能返回 void要 void 就用Action// Funcint,void 编译报错void不能做泛型参数 Actionint act xConsole.WriteLine(x); //正确闭包陷阱lambda 捕获外部变量是捕获变量本身不是当时的值。小结Func... 可以存带返回值方法的泛型委托最后一个泛型参数代表返回类型广泛用于 Lambda、LINQ、回调。如果你需要我可以给你对比Predicate、Func、Action三者区别。相当于 delegate 的语法糖Func 是不是 delegate 的语法糖不完全是语法糖它本身就是编译器预定义好的泛型 delegate 类型。本质public delegate TResult FuncTResult(); public delegate TResult FuncT1,TResult(T1 arg1);Func、Action就是.NET 类库里面已经帮你写好的 delegate 声明不是 C# 编译器给你做的语法转换糖。语法糖编译器看到简写自动帮你生成 delegate 代码比如lambda 是语法糖。 Func是 BCL 已经定义好的委托类型你直接拿来用。对比自己手写 delegate// 自己声明一个委托类型 public delegate int MyCalc(int x);使用内置 Func效果逻辑等价不用自己写 delegate 声明Funcint,int calc;Funcint,int和上面手写的MyCalc是两个完全不同的委托类型虽然签名一模一样但是类型不一样不能直接互相赋值需要转换。MyCalc d1 xx*2; Funcint,int d2 d1; // 编译报错类型不同哪怕签名相同不同 delegate 类型属于不同类型不能隐式转换。这一点很关键。理清概念分层delegate关键字用来声明委托类型。Func / ActionBCL 预先用delegate关键字写好的一堆泛型委托类型直接复用。Lambdax...这才是语法糖编译器把 lambda 生成匿名方法可以赋值给匹配签名的委托可以赋值给 Func、Action、也可以赋值给你自己手写 delegate。通俗总结delegate造委托类型的模具Func微软已经用模具造好一批现成委托类型直接用不用你再拿模具造lambda写委托实例的简写语法糖Predicate 补充PredicateT也是预定义委托delegate bool PredicateT(T obj)等价于FuncT,bool但是独立类型二者也不能直接互赋值。小例子区分//1.自己造委托类型 delegate int MyDel(int a); //2.使用BCL现成委托类型Func Funcint, int funcDel; //3.lambda语法糖赋值给任意匹配签名委托 MyDel d1 x x 1; Funcint, int d2 x x 1; // d1 d2; //编译错误类型不一样 // d2 d1; //编译错误类型不一样 //只能通过lambda/匿名方法中转 Funcint,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 家族全部遵循返回值不能是 voidvoid 交给 Action最多16 个泛型参数FuncT1…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); // ❌ Funcstring, int, bool 无法表达 out int没有语法可以标记outFunc 泛型参数只能代表类型不能修饰参数修饰符ref/out/in。这是最常用的差距。② 可以定义返回 void也可以自定义语义名称Func 不能 void 返回void 用 Action。 但自定义 delegate 可以给委托起业务含义名字// 语义清晰代表事件回调 public delegate void DataChangedEventHandler(object sender, DataChangedArgs e);用Actionobject, DataChangedArgs也能装但名字看不出业务含义。③ 可以超过 16 个参数Func 最多 16 个入参。 如果真的需要 17 个参数的委托只能手写 delegate。现实极少遇到④ 可以定义静态委托、绑定到实例还能用于事件C# 的event事件底层依赖 delegate 类型。你可以把事件声明为自定义 delegate 虽然现在也常用EventHandlerT也是内置 delegate但不能直接把 event 声明成 Func。// 合法 public event DataChangedEventHandler OnDataChange; // ❌编译报错event不能用Func public event Funcint OnDataChange;事件语法要求委托类型Func 是委托类型但C# 语法禁止 event 使用 Func/Action因为事件设计初衷是多播回调Func 有返回值会产生歧义多个返回值取哪个。⑤ 可以定义泛型约束Func 是固定写死的自定义 delegate 可以加泛型约束delegate T CreateDelT() where T : new();内置 Func 是 BCL 已经写死你不能修改它的泛型约束。范围关系图delegate关键字可以定义任意委托类型 ├─ 自己手写 delegate MyDel(...) 【完全自由ref/out、命名、约束、事件】 └─ .NET内置预定义委托 ├─ FuncT… 0‑16参数有返回值不能ref/out ├─ ActionT… 0‑16参数void返回不能ref/out └─ PredicateT、EventHandlerT 等关键误区纠正“Func 是 delegate” —— 这句话对Func 本质就是用 delegate 关键字声明出来的类型。 “delegate 就是 Func” —— 错delegate 是定义工具Func 是工具产出的其中一个成品。什么时候用哪个普通回调、LINQ、lambda优先Func/Action省事。需要ref/out参数必须手写 delegate。写事件优先EventHandlerT不要 Func/Action。需要业务语义名称、泛型约束自定义 delegate。补充小坑即便签名完全一致delegate int MyDel(int x); Funcint, int f;MyDel和Funcint,int是两个完全不同的委托类型不能隐式转换只能通过 lambda 或者.Invoke中转。如果你需要我可以举个 ref 参数委托完整可运行示例。func 是委托的语法糖?结论先说Func 不是委托的语法糖。delegate是关键字用来定义委托类型。FuncT1,TResult是.NET BCL 已经用 delegate 定义好的委托类型是一个实实在在的类型不是编译器帮你偷偷生成的简写。Lambdaxx1才是语法糖。拆开看这个是源码级别的定义.NET 内部就是这么写的public delegate TResult FuncTResult(); public delegate TResult FuncT1, TResult(T1 arg1);这一行是类库代码不是编译器看到你写Func之后临时生成出来的。什么叫语法糖语法糖你写简短写法C# 编译器在编译阶段自动帮你展开成等价代码源码里不存在那个简写对应的类型。举例lambda 就是语法糖Funcint,int f x x * 2;编译器会把这个 lambda 翻译成匿名方法再赋值给委托对象。这是糖。但Funcint,int本身不是糖它就是一个已经存在的委托类型就跟你自己写public delegate int MyDelegate(int x);产出的MyDelegate地位完全一样都是委托类型只是一个是微软写好放库里面一个是你自己写。很多人会混淆的点“用 Func 就不用手写 delegate 了这不就是语法糖吗”不是语法糖这只是复用现成类型省去你手动声明委托类型的工作量。关键证据相同签名不同委托类型不能隐式转换delegate int MyDel(int x); MyDel d1 x1; Funcint,int d2 d1; // ❌编译报错类型不一样如果 Func 只是语法糖编译器应该把二者视为同一类型但实际不是。它们是两个独立的委托类型。Func 有硬边界不支持ref/out/in参数最多 16 个参数。 如果它是编译器语法糖理论上编译器可以帮你生成支持 ref/out 的版本但它做不到因为类型是库里面写死的。层级关系梳理delegate关键字制造委托类型的工具Func/Action工具已经造好的一批现成委托类型BCL 内置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类型早就有了你只需要填类型参数// 没有定义新类型Funcint,int这个类型本来就在BCL里 Funcint, int fn x x * 2;你并没有创造新委托类型只是实例化那个已经存在的泛型委托。关键误区它不会自动识别你的方法然后生成委托很多人以为我写个 lambda编译器看我入参返回自动生成 Func。 不是。是你显式写了Funcint,int编译器才知道要把 lambda 赋值给这个已有的委托类型。// 这行单独写编译报错编译器猜不出来你要什么委托类型 var fn x x * 2;var fn xx*2;直接报错lambda 本身没有类型必须赋值给一个明确的委托类型可以是现成的Funcint,int或者你自己delegate定义的MyCalclambda 本身是 “逻辑片段”没有自身类型必须挂靠到某一个委托类型上。通俗大白话总结你的理解你的原话func 可以系统自己识别你的委托类型而 delegate 要自己设置修正版Func 是系统预先写好一批委托类型你只要填泛型参数就拿来用不用自己新建委托类型 delegate 关键字是用来从零新建一个属于你自己的委托类型。 两者都需要显式指定类型Func 不会自动推断生成委托。核心差异点delegate A(...)定义新委托类型输出一个新类型到程序集FuncTIn,TOut使用已经存在的委托类型不产生新类型Lambda无类型只能赋值给上面两种委托类型之一再强调那个坑哪怕签名一模一样delegate int MyCalc(int x); MyCalc a xx1; Funcint,int b a; // 编译报错类型不一样不能直接赋值虽然方法签名完全匹配但MyCalc和Funcint,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 Funcint, int fnB x x * 2; int resB fnB(5);运行结果resA10resB10执行效果一模一样。所以你感觉这不是一样吗 ✔运行行为一样❌但是类型、编译产物、能否互相赋值完全不一样核心差别编译之后程序集里面发生了什么A 方案自定义 delegatedelegate int MyCalc(int x);编译器会在你的 dll/exe 里生成一个全新的类型MyCalc继承自MulticastDelegate。 这是属于你项目的新类型。B 方案Funcint,intFuncint,int不是你项目生成的它来自系统程序集System.Private.CoreLib.dll早就写死在框架里。 你只是引用这个现成类型你的程序集不会多出任何新委托类型。 一个是 “你造一个新盒子”一个是 “拿系统现成的盒子”。两个盒子外形尺寸一模一样装的方法签名一样但盒子本身不是同一个东西。证据不能互相赋值MyCalc fnA x x * 2; Funcint, int fnB fnA; // ❌编译报错盒子长得一样但是商标不一样不能直接把 A 盒子的东西交给 B 盒子变量。如果它们真的 “完全一样”上面这行就应该编译通过实际不行。那为什么 lambda 又可以同时赋值给两者MyCalc fnA xx*2; Funcint,int fnB xx*2;lambda 本身只是一段逻辑编译器根据左边变量的委托类型帮你包装成对应委托对象。左边是MyCalc→ 包装成MyCalc实例左边是Funcint,int→ 包装成Funcint,int实例lambda 不是生成 Func它适配左边是什么委托就生成什么委托实例。打个生活化比喻delegate int MyCalc(int x)你自己动手定做一个盒子规格输入 int输出 int这个盒子归你的项目。Funcint,int商店.NET 框架已经量产好一模一样规格的盒子你直接买回来用。两个盒子装东西的规则完全相同放进去 5出来 10。 但是来源不一样一个你造一个系统造类型身份不一样不能把定做盒子直接交给商店盒子的变量接收定做盒子你可以改规格比如支持out/ref商店量产的 Func 盒子规格固定死改不了。总结你的困惑“代码写出来效果一样那到底差别在哪”运行调用逻辑相同CLR 类型身份不同编译输出自定义 delegate 会新增类型Func 复用系统已有类型能力边界自定义 delegate 可以 ref/outFunc 做不到日常业务写回调绝大多数场景你感知不到差异所以很多人误以为 Func 就是 delegate 的简写语法糖。但底层类型模型上二者不能等同。如果你想看我可以用伪 IL 展示两者编译后的差别。func 只能定义 int 类型盒子不是Func盒子不是只能 int泛型可以填任意类型。//输入string返回bool Funcstring, bool f1 s s.Length 0; //无参数返回Listint FuncListint f2 () new Listint(); //两个输入string,int返回DateTime Funcstring, int, DateTime f3 (str, num) DateTime.Now;盒子的入参、返回值全部由你填的泛型参数决定string、class、自定义类、结构体都可以。但是盒子的结构是固定死的重点Func 这个 “盒子模具” 是微软写死的Func[T1,T2,...,TResult]前面全部是输入参数的类型最后一个永远是返回值类型只能写类型不能加修饰符ref /out/in✅可以随便换什么类型 ❌不能修改参数的修饰符这是模具本身的限制。举例子Func 做不到的我想要一个委托接收out int参数// 设想但是 C# 不支持泛型占位符不能标记 out Funcstring, 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 FuncUser, string getName u u.Name;没问题随便用自定义 class。Func 还有一个硬限制最多16 个输入参数FuncT1,T2…T16,TResult超过 16 个入参Func 这套模具就没有了只能手写 delegate。极简小结Func 泛型可以填任意数据类型不限于 int。限制不在类型在于参数修饰符 ref/out/in 不支持、最多 16 入参。一旦需要 ref/out只能 delegate 自己造盒子。delegate 和 func 分别支持的数据类型帮我归纳下delegate vs Func 支持的数据 / 参数能力归纳注意这里说的不是能传什么数据类型int/string/class 都支持而是参数修饰符、参数数量、返回值形式普通数据类型两者全都支持。1️⃣ Func系统预定义委托底层public delegate TResult FuncT1,T2,...,TResult(T1 arg1, T2 arg2…)表格项目Func普通值类型✅ int、bool、DateTime、struct 全部支持引用类型✅ string、自定义 class、ListT 全部支持返回值✅必须有返回值不能 voidvoid 请用 Action参数数量✅ 016 个输入参数超过 16 个❌不支持ref参数❌完全不支持out参数❌完全不支持in参数❌完全不支持泛型约束❌不能自定义约束框架已经写死用于 event 事件❌语法禁止使用 Func 做事件类型自定义委托名称❌不能起业务语义名字固定叫 Func关键点Func 的泛型占位符只能描述是什么类型无法携带 ref/out/in 修饰符。Funcstring, int, bool ok; //正常 Funcstring, out int, bool error; //语法报错不允许写out2️⃣ delegate 关键字自定义委托delegate 是用来定义新委托类型的关键字你自己写签名。表格项目自定义 delegate普通值类型✅ int、bool、struct 全部支持引用类型✅ string、自定义 class 全部支持返回值✅ 可以返回任意类型也可以返回 void参数数量✅ 理论无上限写多少个参数都行ref参数✅ 原生支持out参数✅ 原生支持in参数✅ 原生支持泛型约束✅ 可以写where T:xxx泛型约束用于 event 事件✅ 完全支持事件标准用法自定义委托名称✅ 可以给委托起业务名字例如TryParseHandler示例// out、ref、void、泛型约束全都可以 public delegate bool TryHandlerT(string input, out T result) where T : struct;3️⃣ 一张表看清哪些场景该选谁表格场景选 Func/Action必须手写 delegate普通回调、LINQ无 ref/out✅❌回调需要 out /ref/in 参数❌✅写事件 (event)❌✅优先 EventHandlerT入参超过 16 个❌✅想要委托带业务语义名称❌✅需要自定义泛型约束❌✅4️⃣容易混淆的点澄清int、string、自定义类两种都可以用没有区别限制不在于你传什么数据而在于参数的修饰符。Func 本质上也是 delegate是微软用 delegate 关键字定义好的一批委托只是它的签名模板固定死了不支持 ref/out。ActionFunc 的兄弟无返回值同样不支持 ref/out/in。极简记忆口诀Func/Action类型随便填ref/out 不能玩最多 16 参数事件不能干。 自定义 delegate签名我做主ref/out 随便撸事件约束泛型全部都能铺。