Search code examples
c#winforms.net-coredelegateseventhandler

Passing C# function in constructor to be used as a subscriber to EventHandler delegate


I've been spending some time learning about Delegates in C# and have wired up a small Windows Form app containing two forms to test them out.

My goal is to have the AddContactForm push new Contact data back to the ContactsForm using an EventHandler delegate. I currently have this working with the code below, however ultimately I'd like to make ContactAddedHandler in the AddContactForm private, and pass in a function to the AddContactForm constructor, which is used to subscribe to the delegate like so:

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

I've tried making SomeSubscriberFunction various permutations of the following without success:

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

If anyone could provide any guidance on the following two questions, it would be greatly appreciated:

#1. Am I approaching this problem in a completely non-best practice way, if so what is the correct approach.

#2. If this approach using delegates is correct, how can I achieve what I am attempting to do?

Thank you.

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);
         }
     }
 }

Solution

  • Action delegates are quite often used to generate notifications in similar scenarios. You already have a Contact class object, that's the object that stores the required information, so you most probably don't need a custom EventArgs object to wrap the same data.

    For example, if the Action delegate notification is restricted to only the code that creates the Form class:

    Note: written for .NET 6+, since you had the windows-forms-core tag

    public partial class ContactForm : Form {
        public ContactForm() => InitializeComponent();
    
        private void AddContactBtn_Click(object sender, EventArgs e) {
            Action<Contact> contactAdded = (contact) => Debug.WriteLine(contact.Name);
            AddContactForm addContact = new(contactAdded);
            addContact.ShowDialog();
        }
    }
    

    If the Action delegate can only be injected via the Form's Constructor, as shown in this post, you can simply assign the delegate to a private Field and invoke it when needed:

    public partial class AddContactForm : Form {
        Action<Contact>? NotifyNewContact = null;
        public AddContactForm() => InitializeComponent();
        public AddContactForm(Action<Contact> newContact) : this() => NotifyNewContact = newContact;
    
        private void SaveContactBtn_Click(object sender, EventArgs e) {
            NotifyNewContact?.Invoke(new("Test"));
        }
    }
    

    You could also make the Action accessible from the outside and add more than one subscriber.
    You can use a multicast delegate as it was used before. But better create a level of indirection, adding the event keyword, so add() and remove() methods are created under the hood, to protect the object.

    You can do this, in the Constructor of AddContactForm:

    public AddContactForm(Action<Contact> newContact) : this() => NotifyNewContact = newContact;
    // Instead of NotifyNewContact += newContact
    

    but not this, somewhere else:

    addContact.NotifyNewContact = (c) => Debug.WriteLine(c.Name);
    // Instead of += (c) => [...];
    

    public partial class AddContactForm : Form {
        protected internal event Action<Contact>? NotifyNewContact = null;
        public AddContactForm() => InitializeComponent();
        public AddContactForm(Action<Contact> newContact) : this() => NotifyNewContact += newContact;
    
        private void SaveContactBtn_Click(object sender, EventArgs e) {
            NotifyNewContact?.Invoke(new("Test"));
        }
    }
    

    You can then add multiple delegates, where the instance of AddContactForm is visible, to anything else that can pass an Action delegate or in many different other ways:

    public partial class ContactForm : Form {
        // [...]
    
        private void AddContactBtn_Click(object sender, EventArgs e) {
            Action<Contact> contactAdded = (contact) => Debug.WriteLine(contact.Name);
            AddContactForm addContact = new(contactAdded);
    
            // Subscribe a second time, just a lambda
            addContact.NotifyNewContact += (c) => Debug.WriteLine(c.Name);
    
            // Subscribe using a delegate that something else provided
            // Assume otherContact is coming from elsewhere
            Action<Contact> otherContact = new((c)=> Debug.WriteLine(c.Name));
            addContact.NotifyNewContact += otherContact;
    
            // Subscribe using a method that matches the signature
            addContact.NotifyNewContact += ProcessContact;
           
            // [...]
        }
    
       // [...]
        private void ProcessContact(Contact contact) {
            Debug.WriteLine(contact.Name);
        }
    }