简体   繁体   中英

Singleton events

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:

  • no memory is consumed until first Instance call;
  • private serialization/deserialization.

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:

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

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM