当前位置: 首页 > news >正文

委托相关

委托:委托类似于我们的中介

什么时候用委托?当我们普通的调用无法实现的时候 ,就需要使用委托来实现 ,通过用于窗体与窗体之间 ,因为单个窗体没有使用的必要。

  • 举个例子 ,我们为什么需要找中介 ,肯定是我们没法获取方源

经典委托Delegate五步法

  1. 声明委托 ,类的外面 ,与类是同级的 , public delegate void ShowChildValueDelegate();

  2. 创建委托变量 ,在哪里调用就在哪里去创建 ,哪里调用要看数据在什么 ,动作源在哪里

    比如:private ShowChildValueDelegage ShowChildValue

  3. 创建委托方法:哪里实现就在哪里创建 ,明确参数和返回值 ,委托原型与委托方法要保持一致

    private void ShowValue(string value) {}
    
  4. 委托变量绑定委托方法:哪里可以实现绑定就在哪里绑定 ,什么地方可以获取到委托变量和委托方法

    1. = 赋值 ,+= 追加
    2. 单播委托:一个委托变量只需要或允许绑定一个方法 , = 赋值
    3. 多播委托:一个委托变量需要或可能绑定多个方法 , += 追加
    4. 例如:this.SowChildValue = frmChild.ShowValue
  5. 调用委托:希望在哪里调用就在哪里调用 ,如:this.SowChildValue(this.lbl_Temp.Text)

委托的遍历与删除应用

image

基于委托实现子窗体传值

一句话讲清楚
子窗体想把数据传回主窗体?别直接改主窗体的控件!用“委托+事件”——子窗体喊一声“我有数据了!”,主窗体听见后自己去拿。


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. 子窗体:定义委托 + 事件
  2. 子窗体:触发事件传数据
  3. 主窗体:订阅事件 + 写接收方法

搞定!不用记原理,先照着做,做两次就熟了。

多播委托传值多个子窗体

一句话讲明白
一个主窗体打开多个子窗体,每个子窗体都能传数据回来,而且互不干扰——用同一个委托事件,但每个子窗体独立触发,主窗体统一接收。


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,直接赋方法!

搞定!从此“问问题+拿答案”,轻松搞定双向交互。

http://www.hskmm.com/?act=detail&tid=21227

相关文章:

  • 清除“请允许观看视频”通知页面的完整指南
  • 千亿芯片公司被股东“抛弃” ,AI芯片第一股前景几何?
  • Java 与智慧港口:航运调度与物流枢纽数字化
  • DeepSeek-V3.2-Exp 发布,训练推理提效,API 同步降价
  • 图片任意切割工具(Python 3.8 实现)
  • 从零构建能自我优化的AI Agent:Reflection和Reflexion机制对比详解与实现
  • 超精简的小型C编译器
  • Day1 Linux 入门:9 个核心命令(whoami/id/pwd 等)
  • 9.29 闲话
  • MMU的作用
  • 大二学计算机系统基础
  • 20250929 之所思 - 人生如梦
  • 9/29
  • 9.29总结
  • lc1040-移动石子直到连续II
  • 2025年9月29日
  • c++算法学习笔记
  • test5
  • 最高人民法院新劳动争议司法解释一 理解与适用
  • PyPI维护者遭遇钓鱼攻击:假冒登录网站威胁开源供应链安全
  • Tomcat 相关漏洞扫描器(一) - 指南
  • 题解:CF2125E Sets of Complementary Sums
  • 929
  • ManySpeech —— 使用 C# 开发人工智能语音应用
  • 20250929
  • 驱动基础知识速览(迅为RK3568文档)
  • 学习笔记-析合树
  • CSPJ2025模拟赛
  • java代码审计-Shiro认证授权
  • CF868F题解