Singleton Events
I am refactoring old code where I have a lot of static events like
public static event Action Updated;
public static void OnUpdated()
{
if (Updated != null)
Updated();
}
I found out that using lazy singles is often better than using static classes:
- memory is not consumed until the first call
Instance
; - private serialization / deserialization.
So I refactor them to singles and now I have code analysis.
Events like this clearly offend CS1009 , but I have some doubts:
-
sender
does not make sense for static events , why and in what scenariosender
can singleton be used? I can only think of deserialization, but this is the internal implementation of the class (besides, the class is sealed), so I can take care not to use events, and if I need to, I can create a private event. -
Creation
e
(based onEventArgs
) is unnecessary complication to just pass parameters, the big part I hate is moving it to the namespace level, the only thing that addsEventArgs
(can be useful sometimes)Empty
and then you have dozens of classes...EventArgs
. I might think that you sometimes need Cancel or Handled , but I never needed it.
When using an event, everyone is expecting (object sender, SomeEventArgs args)
and is that the only reason?
To summarize, here is my main question (but I would like to clarify other questions as well): CS1009 and singleletons, should I fix events or just suppress the message?
PS: related topics: this , this and this .
I found this question. According to the rule of event design I have to use Event<T>
(not relevant to this question) where is T
based on class EventArgs
.
Regarding sender
in static events:
In static events, the parameter
sender
must be null.
This is a design guide that may not look good to me , but would be greatly welcomed by anyone else (who reads / maintains my code).
He breaks both KISS and YAGNI principles for me. And the more I think about it, the less and less I'm sure what to do.
source to share
I would correct your mistake. A general design guideline is the signature (object sender, EventArgs e)
.
It's a convention and is all about code consistency, code readability ... etc. By following this diagram, you can help other people connect handlers to your events.
Some general tips / answers:
- For a static event, you should really use
null
likesender
(because there is no sender instance in the definition). - If you have nothing to pass for a parameter
e
, use orEventArgs.Empty
instead .new EventArgs()
null
- You can use
EventHandler
andEventHandler<T>
to make it easier to define your events. - For simplicity, you can use your own class
EventArgs<T>
inheriting fromEventArgs
if you want to pass one value to your event handler.
If you want to use a singleton pattern with a definition Lazy<T>
, here is a complete example. Don't mark the event as equal static
, so the parameter sender
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 can also create multiple EventArgs<T1, T2>
if you need to pass two values. Ultimately it EventArgs<Tuple<>>
will also be possible, but for more than two values, I would build a concrete class XXXEventArgs
instead, as it would be easier to read XXXEventArgs.MyNamedBusinessProperty
than EventArgs<T1, T2, T3>.Value2
or EventArgs<Tuple<int, string, bool>>.Value.Item1
.
As for KISS / YAGNI: remember that convention (object sender, EventArgs e)
is all about code consistency. If any developer uses your code to attach a handler to one of your events, I can assure you that they will just love the fact that your event definition is exactly the same as any other event definition in the BCL itself , so it instantly knows how to use your code correctly.
There are even other advantages than just code consistency / readability:
I inherited my custom classes XXXEventArgs
from EventArgs
, but you could build some base class EventArgs
and inherit from it. For example, see MouseEventArgs
and all classes inherited from it. It is much better to replicate the use of existing classes than to provide multiple delegate signatures with 5/6 identical properties. For example:
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 that using it EventArgs
can make the code easier to maintain. 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);
If you want to add a property MyProperty2
to MyEventArgs
, you can do so without changing any 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);
source to share