RemoveListener with 1+ argument UnityAction delegate

Ok, I’ve banged my head around this problem and I can’t manage to get out of it.

I got the RemoveListener working without problems when using the 0 argument delegate, with this code:

UnityAction myAction;

void Start () {
    myAction = () => DoSomething();
    button.onClick.AddListener(myAction);
}

void DoSomething () {
    Debug.Log("Button pressed, removing listener.");
    button.onClick.RemoveListener(myAction);
}

The string shows up in the console, and after the first click the script stops registering the clicks.

Now, if I want to do something like this:

UnityAction<Button> myAction;

void Start () {
    myAction = (arg) => DoSomething(arg);
    button.onClick.AddListener(myAction(button));
}

void DoSomething (Button button) {
    Debug.Log("Button pressed, removing listener.");
    button.onClick.RemoveListener(myAction(button));
}

I get a compile error at line 5, since UnityAction is a void type and not an UnityAction type. So, in order to make the AddListener work, I need to write:

button.onClick.AddListener(delegate{myAction(button);});

or, alternatively:

button.onClick.AddListener(() => myAction(button));

But now I have the final problem that I can’t use the RemoveListener, since the AddListener argument is anonymous and I have no reference to it. The only way to remove it is to remove all listeners, but I really want to know how to remove a single listener when using UnityAction with 1+ arguments.

Any help and insight will be VERY appreciated. :smile:

1 Like

You just use the same construct you used for the no-argument delegate:

button.onClick.AddListener(myAction);
button.onClick.RemoveListener(myAction);

Think of “myAction” as the variable identifier, and doesn’t matter that it points to whatever kind of action taking whatever kind of argument(s). It’s just a function pointer basically.

When you type this:

button.onClick.RemoveListener(myAction(button));

You are invoking myAction and passing its result (void) to RemoveListener(), which doesn’t make sense.

The problem is that when I declare

UnityAction<Button> myAction = (arg) => DoSomething(arg);

and I try to pass myAction without an argument, I get an error:

Error CS1503: Argument 1: cannot convert from ‘UnityEngine.Events.UnityAction<UnityEngine.UI.Button>’ to ‘UnityEngine.Events.UnityAction’ (CS1503)

which is expected, since I’m not passing any argument to it.

BTW, I just checked the source code of UnityEvents.cs, from here https://github.com/MattRix/UnityDecompiled/blob/master/UnityEngine/UnityEngine.Events/UnityEvent.cs and I’m more baffled than before, since there seems to be the methods relative to all UnityAction type (no arg, one arg, etc.), like this:

public void AddListener(UnityAction<T0> call)
{
    base.AddCall(UnityEvent<T0>.GetDelegate(call));
}

so we should have the possibility to use UnityAction<T0[,T1,…]> as parameters, but still I can’t find a way to do that without resorting to anonymous functions.

Edit: I just noticed that all UnityAction<T0[,T1,…]> classes inside the UnityEvent namespace are declared abstract, and only the UnityAction is not. And I didn’t find in the whole solution any Unity class that implements them. Could be this the problem?

1 Like

I think you’re a little confused.

The method AddListener(UnityAction call) only exists for UnityEvent. A button does not declare an UnityEvent but a simple, non-generic UnityEvent. So it only allows to add UnityActions. There’s no mystery about that.

Next, why would you even want to add an UnityAction? It doesn’t make much sense.
If the button accepted an instance of UnityAction, you’d expect the button to pass another argument of type U (where U : T) to your subscribing method.

What you were trying to do is somewhat different. You certainly don’t want to get the argument for ‘DoSomething’ from the button, but use a known instance as a part of your handler that you pass to DoSomething:

button.onClick.AddListener(() => { DoSomething(button); }));

Since you’ve mentioned you cannot remove that listener, it’s as simple as the example written by @Kurt-Dekker .
You just declare a variable of type ‘UnityAction’ that needs to be accessible wherever you want to remove the listener:

Button button;
UnityAction myAction;
void Start()
{
    SomeType someInstance = new SomeType();
    myAction = () => { DoSomething(someInstance); };

    button.onClick.AddListener(myAction);
}

void DoSomething(SomeType someInstance)
{
    // whatever needs to be done
}

void OnDestroy()
{
    button.onClick.RemoveListener(myAction);
}
7 Likes

My mistake was just in thinking that AddListener() had the parametrized UnityAction overloads.

Still wondering why for custom UnityEvents we can override the 1+ parameters abstract AddListener, but they’re unavailable for the onClick event. :expressionless:

Example? I’m sure you’re still a little confused.

As I said, the Button has been implemented using an UnityAction as the developers appeared to have no intention to pass more arguments. What else would you like to get from the button itself?
E.g: (x,y) => { …} suggests x and y will be passed by the caller/sender, they won’t be initially set by your event handler.

The only thing the devs could’ve provided would be more information about the sender and some general information. A common convention used to be sender : object, args : TEventArgs where TEventArgs : System.EventArgs.