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