Search code examples
c#nullactionprivate

Prevent Action being set to null (except privately)


I'm trying to write code in a way that limits the surface area for error, by exposing only what I want the public users to be able to play with.

In this case, I have an Action that I want public users to be able to subscribe or unsubscribe to. However, I don't want those users to be able to adversely affect OTHER users, i.e. by unsubscribing EVERYBODY.

public class X
{
    public static Action a;
}

public class Mistake
{
    public void Oops()
    {
        X.a = null;
        // I just cleared ALL subscribers to X.a - not just my own subscription.
    }
}

One way around this is to hide the Action itself, and instead present methods to sub/unsub to it:

public class X
{
    static Action _a;

    public static void SubA(Action cb) =>
        _a += cb;
    public static void UnsubA(Action cb) =>
        _a -= cb;
}

This works - it's already a lot harder to make a similar kind of mistake. However, it's a little more verbose than I'd like.

Hence my question:

Is there any "nice"/idiomatic way to allow public X.a += ... and X.a -= ..., but disallow public X.a = null;?

I tried using an auto-property public static Action A { get; private set; }, but as expected, that prevented usage of += and -= publicly.

I suspect that there may be a way to put a wrapping property around X.a whose setter throws/does nothing when value == null, but even if that worked, I feel that it's still unpleasantly verbose.

If this isn't possible with Action, but something approximately similar is possible with event/delegate (which I know very little about), I'd also be interested!

Hope you're all enjoying the festive season 🙂


Solution

  • This might work for you, depending exactly what you need

    private Action _myActon;
    
    public event Action MyAction
    {
       add => _myActon += value;
       remove => _myActon -= value;
    } 
    
    ...
    
    MyAction += () => Console.WriteLine("test1");
    MyAction += () => Console.WriteLine("test2");
    MyAction += null;
    //MyAction = null // compiler error;
    
    ...
    
    _myActon.Invoke();
    _myActon= null;
    

    Output

    test1
    test2
    

    To the compiler Events and Delegate have a lot in common, using event keywords and the above pattern limits the ability to assign null, there is also some subtle other differences, however I'll try and find some reputable sources.


    Additional Resources

    A detailed description of events and delegates, lots of fun for the whole family here

    • Delegates and Events

      • The event Keyword

        The event keyword indicates to the compiler that the delegate can be invoked only by the defining class, and that other classes can subscribe to and unsubscribe from the delegate using only the appropriate += and -= operators, respectively.

    • How to: Implement Custom Event Accessors (C# Programming Guide)

      • An event is a special kind of multicast delegate that can only be invoked from within the class that it is declared in. Client code subscribes to the event by providing a reference to a method that should be invoked when the event is fired. These methods are added to the delegate's invocation list through event accessors, which resemble property accessors, except that event accessors are named add and remove.

    • add (C# Reference)

      • The add contextual keyword is used to define a custom event accessor that is invoked when client code subscribes to your event. If you supply a custom add accessor, you must also supply a remove accessor.

    • Delegates and Events (C# in Depth) Jon Skeet