委托:委托类似于我们的中介
什么时候用委托?当我们普通的调用无法实现的时候 ,就需要使用委托来实现 ,通过用于窗体与窗体之间 ,因为单个窗体没有使用的必要。
- 举个例子 ,我们为什么需要找中介 ,肯定是我们没法获取方源
经典委托Delegate五步法
-
声明委托 ,类的外面 ,与类是同级的 ,
public delegate void ShowChildValueDelegate();
-
创建委托变量 ,在哪里调用就在哪里去创建 ,哪里调用要看数据在什么 ,动作源在哪里
比如:
private ShowChildValueDelegage ShowChildValue
-
创建委托方法:哪里实现就在哪里创建 ,明确参数和返回值 ,委托原型与委托方法要保持一致
private void ShowValue(string value) {}
-
委托变量绑定委托方法:哪里可以实现绑定就在哪里绑定 ,什么地方可以获取到委托变量和委托方法
-
= 赋值 ,+= 追加
- 单播委托:一个委托变量只需要或允许绑定一个方法 ,
= 赋值
- 多播委托:一个委托变量需要或可能绑定多个方法 ,
+= 追加
- 例如:
this.SowChildValue = frmChild.ShowValue
-
-
调用委托:希望在哪里调用就在哪里调用 ,如:
this.SowChildValue(this.lbl_Temp.Text)
委托的遍历与删除应用
基于委托实现子窗体传值
一句话讲清楚:
子窗体想把数据传回主窗体?别直接改主窗体的控件!用“委托+事件”——子窗体喊一声“我有数据了!”,主窗体听见后自己去拿。
1. 为什么用委托?(别跳过!)
- ❌ 错误做法:在子窗体里直接写
主窗体.文本框.Text = "xxx"
→ 会报错、耦合高、代码乱成一锅粥。 - ✅ 正确做法:子窗体只负责“通知”,主窗体自己“接收并处理”。
→ 安全、清晰、好维护。
2. 三步搞定(超简单)
第一步:在子窗体里定义“喊话工具”
在子窗体(比如叫 Form2
)里写:
// 1. 声明一个委托(相当于定义“喊话”的格式)
public delegate void SendDataHandler(string msg);// 2. 声明一个事件(相当于“喇叭”)
public event SendDataHandler DataSent;
小白提示:这两行写在
public partial class Form2 : Form
里面,但别写在方法里!
第二步:子窗体“喊话”
比如点一个“确定”按钮时:
private void btnOK_Click(object sender, EventArgs e)
{// 3. 通过事件“喊话”,把数据传出去DataSent?.Invoke(textBox1.Text); // 把文本框内容喊出去this.Close(); // 关闭子窗体
}
✅
?.
是安全调用:如果没人听(没订阅),就不喊,不会报错。
第三步:主窗体“监听并接收”
在主窗体(比如 Form1
)打开子窗体的地方:
private void btnOpen_Click(object sender, EventArgs e)
{Form2 f2 = new Form2();// 4. 订阅事件:告诉子窗体“我听着呢!”f2.DataSent += F2_DataSent; f2.ShowDialog(); // 模态打开(等子窗体关了才继续)
}
然后写一个接收方法:
// 5. 这个方法会被自动调用,msg 就是传过来的数据
private void F2_DataSent(string msg)
{// 把数据放到主窗体的文本框里txtResult.Text = msg;
}
3. 完整代码结构(照着抄就行)
子窗体 Form2.cs
public partial class Form2 : Form
{// 1. 委托public delegate void SendDataHandler(string msg);// 2. 事件public event SendDataHandler DataSent;public Form2(){InitializeComponent();}private void btnOK_Click(object sender, EventArgs e){// 3. 喊话DataSent?.Invoke(textBox1.Text);this.Close();}
}
主窗体 Form1.cs
public partial class Form1 : Form
{public Form1(){InitializeComponent();}private void btnOpen_Click(object sender, EventArgs e){Form2 f2 = new Form2();f2.DataSent += F2_DataSent; // 4. 监听f2.ShowDialog();}private void F2_DataSent(string msg) // 5. 接收{txtResult.Text = msg;}
}
4. 小白常见问题
Q:为什么不能直接 Form1 f1 = new Form1(); f1.txt.Text = ...
?
A:你 new 出来的是新窗体,不是屏幕上那个!改了也看不到。
Q:DataSent?.Invoke(...)
中的 ?
是啥?
A:防止没人监听时报错。有订阅就喊,没订阅就安静。
Q:能不能传多个值?
A:可以!改委托就行:
public delegate void SendDataHandler(string name, int age);
DataSent?.Invoke("张三", 20);
接收方法也要对应改参数。
5. 记住这个套路
- 子窗体:定义委托 + 事件
- 子窗体:触发事件传数据
- 主窗体:订阅事件 + 写接收方法
搞定!不用记原理,先照着做,做两次就熟了。
多播委托传值多个子窗体
一句话讲明白:
一个主窗体打开多个子窗体,每个子窗体都能传数据回来,而且互不干扰——用同一个委托事件,但每个子窗体独立触发,主窗体统一接收。
1. 什么是“多播委托”?
- 委托可以“绑多个方法”,叫多播(Multicast)。
- 但在窗体传值场景里,我们不是让一个事件触发多个方法,而是:
多个子窗体各自触发同一个类型的事件,主窗体分别监听每个子窗体。 - 小白重点:每个子窗体都要单独订阅!
2. 实现目标
- 主窗体有一个按钮,点一次开一个子窗体(可开多个)。
- 每个子窗体有个输入框和“发送”按钮。
- 点“发送”,把内容传回主窗体的列表框(ListBox)里。
- 多个子窗体同时开着,各自传值互不影响。
3. 三步搞定(照着抄)
第一步:子窗体定义委托和事件(和单个一样)
// FormChild.cs(子窗体)
public partial class FormChild : Form
{// 1. 定义委托public delegate void DataSendHandler(string data);// 2. 定义事件public event DataSendHandler OnDataSend;public FormChild(){InitializeComponent();}private void btnSend_Click(object sender, EventArgs e){// 3. 触发事件,传出数据OnDataSend?.Invoke(txtInput.Text);// 注意:这里不关闭窗体!可以多次发送}
}
✅ 子窗体不关,方便多次传值。
第二步:主窗体打开多个子窗体,并分别订阅
// FormMain.cs(主窗体)
int childCount = 0; // 用来区分子窗体编号private void btnOpenChild_Click(object sender, EventArgs e)
{childCount++;FormChild child = new FormChild();// 4. 给每个子窗体单独订阅事件!child.OnDataSend += (data) =>{// 匿名方法:直接写处理逻辑listBox1.Items.Add($"子窗体{childCount}传回: {data}");};child.Text = $"子窗体 {childCount}"; // 设置标题方便区分child.Show(); // 用 Show(),不是 ShowDialog()
}
关键:每次 new 一个子窗体,都要重新订阅一次事件!
用childCount
区分是哪个子窗体传的。
第三步:运行效果
- 点三次“打开子窗体” → 弹出三个窗体。
- 在子窗体1输入“你好”,点发送 → 主窗体列表显示:“子窗体1传回: 你好”
- 在子窗体3输入“测试”,点发送 → 显示:“子窗体3传回: 测试”
4. 完整代码(直接复制就能跑)
子窗体 FormChild.cs
public partial class FormChild : Form
{public delegate void DataSendHandler(string data);public event DataSendHandler OnDataSend;public FormChild(){InitializeComponent();}private void btnSend_Click(object sender, EventArgs e){OnDataSend?.Invoke(txtInput.Text);}
}
主窗体 FormMain.cs
public partial class FormMain : Form
{int childCount = 0;public FormMain(){InitializeComponent();}private void btnOpenChild_Click(object sender, EventArgs e){childCount++;FormChild child = new FormChild();// 匿名方法订阅,带编号标识int currentId = childCount; // 捕获当前编号(避免闭包问题)child.OnDataSend += (data) =>{listBox1.Items.Add($"子窗体{currentId}传回: {data}");};child.Text = $"子窗体 {childCount}";child.Show();}
}
⚠️ 注意:
int currentId = childCount;
这行很重要!
如果直接用childCount
,所有子窗体都会显示最后一个编号(闭包陷阱)。
5. 小白避坑指南
问题 | 原因 | 解决 |
---|---|---|
所有子窗体都显示“子窗体3” | 闭包捕获的是变量引用,不是值 | 用 int currentId = childCount; 拷贝一份 |
点发送没反应 | 忘了订阅事件 | 每次 new 子窗体后必须 += |
只能传一次 | 用了 ShowDialog() 并且关窗了 |
用 Show() ,别关窗 |
主窗体收不到数据 | 事件名写错 / 没触发 | 检查 OnDataSend?.Invoke(...) 是否执行 |
6. 能不能传复杂数据?
当然可以!比如传姓名+年龄:
// 委托改成
public delegate void DataSendHandler(string name, int age);// 子窗体触发
OnDataSend?.Invoke(txtName.Text, int.Parse(txtAge.Text));// 主窗体接收
child.OnDataSend += (name, age) =>
{listBox1.Items.Add($"{name}, {age}岁");
};
7. 记住这个模式
- 子窗体:只管喊(触发事件)
- 主窗体:谁喊我听谁(每个实例单独订阅)
- 多个子窗体 = 多个独立实例 + 多次订阅
搞定!多播委托在窗体传值中,核心就是每个子窗体独立通信,不是“一个事件多人听”,而是“每人一个喇叭,主窗体全装上”。
系统委托Action简化Delegate
一句话总结:
Action
是 C# 自带的“万能委托”,不用自己写 delegate
,写起来更快、更干净!
1. 以前怎么写(麻烦版)
想让子窗体传个字符串回主窗体,以前得:
// 1. 先声明委托类型
public delegate void MyHandler(string msg);// 2. 再声明事件
public event MyHandler DataSent;// 3. 触发
DataSent?.Invoke("hello");
三行代码,其实就干一件事:传一个字符串。
2. 现在怎么写(Action 简化版)
直接用系统自带的 Action<T>
:
// 1. 直接声明事件,不用写 delegate!
public event Action<string> DataSent;// 2. 触发方式完全一样
DataSent?.Invoke("hello");
✅ 少写一行代码
✅ 不用起名字(不用 MyHandler
)
✅ 功能完全一样!
3. Action 能传几个参数?
-
Action
:无参数public event Action Clicked; Clicked?.Invoke();
-
Action<T>
:1个参数public event Action<string> MessageSent; MessageSent?.Invoke("hi");
-
Action<T1, T2>
:2个参数public event Action<string, int> UserInfoSent; UserInfoSent?.Invoke("张三", 25);
-
最多支持 16 个参数(但别真写那么多!)
记住:Action 不能有返回值。要返回值用
Func<T>
(以后再说)。
4. 子窗体传值实战(Action 版)
子窗体 Form2.cs
public partial class Form2 : Form
{// 直接用 Action<string>,不用自己定义 delegate!public event Action<string> DataSent;private void btnSend_Click(object sender, EventArgs e){DataSent?.Invoke(textBox1.Text); // 传值}
}
主窗体 Form1.cs
private void btnOpen_Click(object sender, EventArgs e)
{Form2 f2 = new Form2();// 订阅事件(写法不变)f2.DataSent += (msg) =>{textBox1.Text = msg;};f2.Show();
}
✅ 代码更短
✅ 逻辑更清晰
✅ 新手不容易写错
5. 对比:传统 delegate vs Action
写法 | 传统 delegate | Action |
---|---|---|
声明委托 | public delegate void Handler(string s); |
不用声明 |
声明事件 | public event Handler MyEvent; |
public event Action<string> MyEvent; |
触发 | MyEvent?.Invoke("x"); |
MyEvent?.Invoke("x"); |
代码行数 | 多1行 | 少1行 |
易错点 | 名字写错、重复定义 | 几乎不会错 |
6. 小白注意事项
-
✅ Action 适合“只传数据,不需要返回” 的场景(比如窗体传值、按钮点击通知)。
-
❌ 不能用 Action 返回值。比如想让子窗体问主窗体“要不要保存?”,主窗体返回 true/false —— 这时候用
Func<bool>
。 -
Action 是 .NET 内置的,不用 using,直接用。
-
记住常用形式:
-
Action
→ 无参 -
Action<string>
→ 传一个字符串 -
Action<string, int>
→ 传字符串和数字
7. 一句话口诀
传数据不用返回?直接上
Action<T>
!
别再手写 delegate,系统给你准备好!
搞定!从此写委托,又快又稳不翻车。
系统委托带返回值Func应用
一句话讲清楚:
Action
只能“发消息”,不能“要答案”;
Func
能“问问题 + 拿结果”,比如:“主窗体,这个用户名能用吗?”,主窗体回答 true
或 false
。
1. Func 是啥?和 Action 有啥区别?
Action | Func | |
---|---|---|
作用 | 执行动作,无返回值 | 执行动作,有返回值 |
例子 | 发送消息、通知关闭 | 验证数据、获取配置、计算结果 |
写法 | Action<string> |
Func<string, bool> |
返回值 | ❌ 没有 | ✅ 最后一个类型就是返回值! |
✅ Func 的最后一个泛型参数 = 返回值类型
比如:Func<int, string, bool>
表示:传一个int
和一个string
,返回bool
2. 最常用写法(记住这3个够用)
Func<bool> // 无参数,返回 bool
Func<string, bool> // 传 string,返回 bool
Func<string, int, string> // 传 string + int,返回 string
3. 实战场景:子窗体问主窗体“用户名是否重复?”
需求:
子窗体输入用户名 → 点“检查” → 主窗体判断是否已存在 → 返回 true/false
第一步:子窗体定义 Func 委托字段(不是事件!)
// FormChild.cs
public partial class FormChild : Form
{// 注意:这里用字段,不是 event!因为要调用并拿返回值public Func<string, bool> CheckUsername;private void btnCheck_Click(object sender, EventArgs e){string input = txtUsername.Text;// 调用主窗体的方法,并拿到返回值!if (CheckUsername != null){bool isExist = CheckUsername(input);lblResult.Text = isExist ? "已存在" : "可用";}}
}
⚠️ 重点:用字段(Field)或属性(Property),不能用 event
因为事件不能直接调用拿返回值!
第二步:主窗体赋值给这个 Func
// FormMain.cs
private void btnOpenChild_Click(object sender, EventArgs e)
{FormChild child = new FormChild();// 把主窗体的验证方法“塞给”子窗体child.CheckUsername = IsUsernameExist;child.Show();
}// 主窗体的验证方法(返回 bool)
private bool IsUsernameExist(string username)
{// 模拟数据库检查List<string> existNames = new List<string> { "admin", "root", "test" };return existNames.Contains(username);
}
✅ 子窗体调用 CheckUsername("admin")
→ 主窗体执行 IsUsernameExist("admin")
→ 返回 true
4. 匿名方法 / Lambda 写法(更简洁)
主窗体也可以不写单独方法,直接用 Lambda:
child.CheckUsername = (name) =>
{return name == "admin" || name == "guest";
};
或者更短:
child.CheckUsername = name => name == "admin";
5. 常见错误 & 小白避坑
错误 | 原因 | 正确做法 |
---|---|---|
用 event Func<...> |
事件不能获取返回值 | 用普通字段或属性 |
忘记判空 if (CheckUsername != null) |
没赋值就调用会报错 | 调用前先判空 |
返回值类型写错 | 比如写成 Func<string> 但方法返回 int |
确保方法返回类型和 Func 最后一个泛型一致 |
6. 对比:Action vs Func 使用场景
- 用 Action:
“主窗体,我关了!”
“主窗体,用户点了保存!”
→ 只通知,不需要回答。 - 用 Func:
“主窗体,这个ID存在吗?” → 要true/false
“主窗体,给我配置路径!” → 要string
→ 需要返回结果。
7. 一句话口诀
要返回值?用
Func
!
最后一个泛型是答案,前面全是问题参数。
别用 event,直接赋方法!
搞定!从此“问问题+拿答案”,轻松搞定双向交互。