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 scenario sender

    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 on EventArgs

    ) is unnecessary complication to just pass parameters, the big part I hate is moving it to the namespace level, the only thing that adds EventArgs

    (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.

+3


source to share


1 answer


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

    like sender

    (because there is no sender instance in the definition).
  • If you have nothing to pass for a parameter e

    , use or EventArgs.Empty

    instead .new EventArgs()

    null

  • You can use EventHandler

    and EventHandler<T>

    to make it easier to define your events.
  • For simplicity, you can use your own class EventArgs<T>

    inheriting from EventArgs

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

      

+2


source







All Articles