You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在C#构造函数中传递函数作为EventHandler委托订阅者的实现疑问

问题描述

我正在学习C#委托,搭建了一个双窗体的Windows Form应用做测试,目标是让AddContactForm通过EventHandler委托将新建的Contact数据传递回ContactsForm。现有代码可正常运行,但希望把AddContactForm中的ContactAddedHandler设为私有,并通过构造函数传入函数来订阅该委托,示例思路如下:

public AddContactForm(SomeSubscriberFunction foo)
{
    ContactAddedHandler += foo;
}

我尝试将SomeSubscriberFunction定义为以下几种形式均未成功:

  • Func<object?, AddContactEventArgs?>
  • Func<AddContactEventArgs?>
  • Action<object?, AddContactEventArgs?>

需要解决两个问题:

  1. 这种实现方式是否不符合最佳实践?如果是,正确方式是什么?
  2. 若该委托方式可行,如何实现需求?

附现有可运行代码:

ContactsForm 现有代码

public void addContactBtn_Click(Object sender, EventArgs e)
{
    AddContactForm addContactForm = new AddContactForm();
    addContactForm.ContactAddedHandler += ContactAdded;
    addContactForm.Show();
}

public void ContactAdded(object? sender, AddContactForm.AddContactEventArgs e)
{
    Console.Write("Contact Added");
}

AddContactForm 现有代码

public partial class AddContactForm : Form
{
    public class AddContactEventArgs(Contact contact) : EventArgs 
    {
        public Contact contact = contact;
    }

    public event EventHandler<AddContactEventArgs>? ContactAddedHandler;

    public AddContactForm()
    {
        InitializeComponent();
    }

    private void saveContactBtn_Click(object sender, EventArgs e)
    {
        Contact c = new Contact("Test");
        AddContactEventArgs args = new AddContactEventArgs(c);
        if (ContactAddedHandler != null)
        {
            ContactAddedHandler(this, args);
        }
    }
}

解答

问题1:实现方式的最佳实践分析

这种通过构造函数传入订阅方法、将事件设为私有的方式并非WinForms中的常规最佳实践,但也并非不可行,具体取决于你的封装需求:

  • 常规WinForms做法是保持事件公开(如你现有代码的public event EventHandler<AddContactEventArgs>? ContactAddedHandler),让外部窗体自由订阅/取消订阅事件。这种方式更灵活,符合.NET事件模型的设计初衷,外部调用方可以根据需要管理订阅生命周期。
  • 若你希望严格封装AddContactForm,避免外部随意修改事件订阅,通过构造函数传入订阅方法的方式是合理的,它能确保事件仅在初始化时被订阅,减少外部误操作的可能。

问题2:委托方式的具体实现

你的核心问题是委托类型不匹配:EventHandler<AddContactEventArgs>的本质是返回值为void、参数为object?和AddContactEventArgs的委托,对应的预定义委托类型是Action<object?, AddContactEventArgs>(注意不要给AddContactEventArgs加可空标记?,因为触发事件时传入的是非空实例)。

以下是修改后的完整代码:

修改后的 ContactsForm 代码

public void addContactBtn_Click(Object sender, EventArgs e)
{
    // 传入ContactAdded方法作为构造函数参数
    AddContactForm addContactForm = new AddContactForm(ContactAdded);
    addContactForm.Show();
}

public void ContactAdded(object? sender, AddContactForm.AddContactEventArgs e)
{
    Console.Write("Contact Added");
}

修改后的 AddContactForm 代码

public partial class AddContactForm : Form
{
    public class AddContactEventArgs(Contact contact) : EventArgs 
    {
        public Contact contact = contact;
    }

    // 将事件设为私有
    private event EventHandler<AddContactEventArgs>? ContactAddedHandler;

    // 构造函数接受EventHandler<AddContactEventArgs>类型的委托参数
    public AddContactForm(EventHandler<AddContactEventArgs> subscriber)
    {
        InitializeComponent();
        ContactAddedHandler += subscriber;
    }

    private void saveContactBtn_Click(object sender, EventArgs e)
    {
        Contact c = new Contact("Test");
        AddContactEventArgs args = new AddContactEventArgs(c);
        // 用空合并运算符简化触发逻辑
        ContactAddedHandler?.Invoke(this, args);
    }
}

补充说明

  • 若需要支持多个订阅者,可让构造函数接受IEnumerable<EventHandler<AddContactEventArgs>>类型的参数,循环添加订阅。
  • 触发事件时,?.Invoke()的写法比传统的null判断更简洁安全。

内容的提问来源于stack exchange,提问作者Acadian_Ghost

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 19:07:02