I am refactoring some old code, where I have a lot of static events like this
public static event Action Updated;
public static void OnUpdated()
{
if (Updated != null)
Updated();
}
I found out what using lazy singletons is often better than using static classes:
Instance
call; So that I refactor those to singletons and now I have Code Analysis complains.
Such events clearly offend CS1009 but I have some doubts:
for static events sender
doesn't makes sense, why and in what scenario would singleton sender
be of any use? I can only think about deserialization, but it's an internal class implementation (moreover class is sealed), so I can take care to not use events and if I need one, I can create private event.
creating e
(deriving from EventArgs
) is unnecessary complication to simply pass parameters, the biggest part I hate is to move it into namespace level, the only thing which EventArgs
adds (could be useful sometimes) is Empty
and then you have dozens of classes ...EventArgs
. I could think what sometimes you need Cancel or Handled mechanics, but it was neverd needed for me.
When using event does everybody expect (object sender, SomeEventArgs args)
and this is the only reason?
To summarize, here is my main question (but I would appreciate clarifying other questions as well): CS1009 and singletons, should I fix events or simply suppress the message?
PS: related subjects: this , this and this .
I found this question. According to event design guidelines I have to use Event<T>
(disregards to this question), where T
is based on EventArgs
class.
Regarding sender
in static events:
On static events, the
sender
parameter should be null.
This is a design guideline , which may not looks pretty to me , but will be very welcomed by anybody else (who is reading/maintaining my code).
It breaks both KISS and YAGNI principles as for me. And the more I think about it, the less and less I am sure of what to do.
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:
null
as sender
(because there is no sender instance per definition ). e
parameter, use EventArgs.Empty
instead of new EventArgs()
or null
. EventHandler
and EventHandler<T>
to simplify the definition of your events. 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);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.