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