I would fix your error. The general design guideline is indeed the (object sender, EventArgs e)
signature.
It's a convention and is all about code consistency, code readability...etc. Following this pattern will help other people attaching handlers to your events.
Some general tips/answers:
- For a static event, you should indeed use
null
as sender
(because there is no sender instance per definition).
- If you have nothing to pass for the
e
parameter, use EventArgs.Empty
instead of new EventArgs()
or null
.
- You could use
EventHandler
and EventHandler<T>
to simplify the definition of your events.
- For the sake of simplicity, you could use a custom
EventArgs<T>
class inheriting from EventArgs
if you want to pass a single value to your event handler.
If you want to use the singleton pattern with a Lazy<T>
definition, here's a complete example. Do note no event is static
, so the sender
parameter contains a reference to the singleton instance:
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
this.Value = value;
}
public T Value { get; set; }
}
public class EventArgs2 : EventArgs
{
public int Value { get; set; }
}
internal static class Program
{
private static void Main(string[] args)
{
Singleton.Instance.MyEvent += (sender, e) => Console.WriteLine("MyEvent with empty parameter");
Singleton.Instance.MyEvent2 += (sender, e) => Console.WriteLine("MyEvent2 with parameter {0}", e.Value);
Singleton.Instance.MyEvent3 += (sender, e) => Console.WriteLine("MyEvent3 with parameter {0}", e.Value);
Singleton.Instance.Call();
Console.Read();
}
}
public sealed class Singleton
{
private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance { get { return lazy.Value; } }
/// <summary>
/// Prevents a default instance of the <see cref="Singleton"/> class from being created.
/// </summary>
private Singleton()
{
}
/// <summary>
/// Event without any associated data
/// </summary>
public event EventHandler MyEvent;
/// <summary>
/// Event with a specific class as associated data
/// </summary>
public event EventHandler<EventArgs2> MyEvent2;
/// <summary>
/// Event with a generic class as associated data
/// </summary>
public event EventHandler<EventArgs<int>> MyEvent3;
public void Call()
{
if (this.MyEvent != null)
{
this.MyEvent(this, EventArgs.Empty);
}
if (this.MyEvent2 != null)
{
this.MyEvent2(this, new EventArgs2 { Value = 12 });
}
if (this.MyEvent3 != null)
{
this.MyEvent3(this, new EventArgs<int>(12));
}
Console.Read();
}
}
EDIT:
You could also build some EventArgs<T1, T2>
if you need to pass two values. Ultimately, EventArgs<Tuple<>>
would also be possible, but for more than 2 values I would build a specific XXXEventArgs
class instead as it's easier to read XXXEventArgs.MyNamedBusinessProperty
than EventArgs<T1, T2, T3>.Value2
or EventArgs<Tuple<int, string, bool>>.Value.Item1
.
Regarding KISS/YAGNI: remember the (object sender, EventArgs e)
convention is all about code consistency. If some developer uses your code to attach an handler to one of your events, I can assure you he will just love the fact that your event definition is just like any other event definition in the BCL itself, so he instantly knows how to properly use your code.
There even are others advantages than just code consistency/readability:
I inherited my custom XXXEventArgs
classes from EventArgs
, but you could build some base EventArgs
class and inherit from it. For instance, see MouseEventArgs
and all classes inheriting from it. Much better to reuse existing classes than to provide multiple delegate signatures with 5/6 identical properties. For instance:
public class MouseEventArgs : EventArgs
{
public int X { get; set; }
public int Y { get; set; }
}
public class MouseClickEventArgs : MouseEventArgs
{
public int ButtonType { get; set; }
}
public class MouseDoubleClickEventArgs : MouseClickEventArgs
{
public int TimeBetweenClicks { get; set; }
}
public class Test
{
public event EventHandler<MouseClickEventArgs> ClickEvent;
public event EventHandler<MouseDoubleClickEventArgs> DoubleClickEvent;
}
public class Test2
{
public delegate void ClickEventHandler(int X, int Y, int ButtonType);
public event ClickEventHandler ClickEvent;
// See duplicated properties below =>
public delegate void DoubleClickEventHandler(int X, int Y, int ButtonType, int TimeBetweenClicks);
public event DoubleClickEventHandler DoubleClickEvent;
}
Another point is, using EventArgs
could simplify code maintainability. Imagine the following scenario:
public MyEventArgs : EventArgs
{
public string MyProperty { get; set; }
}
public event EventHandler<MyEventArgs> MyEvent;
...
if (this.MyEvent != null)
{
this.MyEvent(this, new MyEventArgs { MyProperty = "foo" });
}
...
someInstance.MyEvent += (sender, e) => SomeMethod(e.MyProperty);
In case you want to add some MyProperty2
property to MyEventArgs
, you could do it without modifying all your existing event listeners:
public MyEventArgs : EventArgs
{
public string MyProperty { get; set; }
public string MyProperty2 { get; set; }
}
public event EventHandler<MyEventArgs> MyEvent;
...
if (this.MyEvent != null)
{
this.MyEvent(this, new MyEventArgs { MyProperty = "foo", MyProperty2 = "bar" });
}
...
// I didn't change the event handler. If SomeMethod() doesn't need MyProperty2, everything is just fine already
someInstance.MyEvent += (sender, e) => SomeMethod(e.MyProperty);