Home > front end >  Assigning methods to action won't clear previous assigned methods
Assigning methods to action won't clear previous assigned methods

Time:03-25

I have this function that i call multiple times so i need to wipe the assigned actions and make sure they don't duplicate:

My OnClick looks like this:

public class Input {
    public Action<Vector3> OnClick; 
}

Then in a different class I have:

// _input is a refence to class Input
public void Init()
{        
    _input.OnClick -= OnLeftClick;
    _input.OnClick  = OnLeftClick;

    var list = _input.OnClick.GetInvocationList();

    var list = _input.OnClick.GetInvocationList();
    Debug.Log("DEBUG: "   list.Length);
    foreach (var item in list)
        Debug.Log(item.Method.ToString());
}

After calling this function a few times in a row the output gives:

DEBUG: 1
Void OnLeftClick(Vector3)
DEBUG: 2 
Void OnLeftClick(Vector3)
Void OnLeftClick(Vector3)
DEBUG: 5
Void OnLeftClick(Vector3)
Void OnLeftClick(Vector3)
Void OnLeftClick(Vector3)
Void OnLeftClick(Vector3)
Void OnLeftClick(Vector3)

So my method OnLeftClick is getting called multiple times, but I don't know why. How do you clear the method from the action so I can then re-assign it to prevent duplicates?

CodePudding user response:

When public Action<Vector3> OnClick; is not marked event, i.e. it is a pure field, everyone has access to it in its entirety (not just through add and remove accessors). So they can clear it with _input.OnClick = null;.

However, are you sure you do not want an event?

For delegates, the convention is that a null reference is used (rather than some empty object) when there are no functions.

If instead you decide for an auto-event (so-called field-like event), access to the automatic (implicit) backing field is only from inside the class itself. Of course you can create a method in the class that clears the event, and make the method nonprivate, but maybe it is not the best design.

CodePudding user response:

The problem here is that your OnClick event subscribers are different from one Init call to another. Probably you are invoking the Init method on different objects or reference.

I hope this example clarify the situation:

class A {
   public Action<Vector3> OnClick;
   // using method group
   public A() : this(_OnClick) { }
   // using anonymous method/action reference
   public A(Action<Vector3> a) => OnClick = a;
   ...
   public void Init() {
      _init.OnClick -= OnClick;
      _init.OnClick  = OnClick;
   }
   public void _OnClick(Vector3 v) { }
}

Problem with group method

A a1 = new A();
a1.Init();
// I will display the subscriber list like: _init.OnClick -> []
// Here you are applying:
//   _init.OnClick -= a1.OnClick; -> _init.OnClick [ ]
//   _init.OnClick  = a1.OnClick; -> _init.OnClick [ a1.OnClick ]
// the subscriber is a1
A a2 = new A();
// But here you are applying:
//   _init.OnClick -= a2.OnClick; -> _init.OnClick [ a1.OnClick ]
//   _init.OnClick  = a2.OnClick; -> _init.OnClick [ a1.OnClick, a2.OnClick ]
// the subscriber is a2
a2.Init();

Problem with anonymous methods

A a = new A(v => Console.WriteLine($"A1: {v}"));
a.Init();
// Here you are applying:
//   _init.OnClick -= a.OnClick; -> _init.OnClick [ ]
//   _init.OnClick  = a.OnClick; -> _init.OnClick [ (v => Console.WriteLine($"A1: {v}")) ]
// the subscriber is (v => Console.WriteLine($"A1: {v}"))
a.OnClick = v => Console.WriteLine($"A2: {v}");
a.Init();
// But here you are applying:
//   _init.OnClick -= a.OnClick; -> _init.OnClick [ (v => Console.WriteLine($"A1: {v}")) ]
// and a.OnClick is (v => Console.WriteLine($"A2: {v}"))
//   _init.OnClick  = a.OnClick; -> _init.OnClick [ (v => Console.WriteLine($"A1: {v}")), (v => Console.WriteLine($"A2: {v}")) ]
// the subscriber is (v => Console.WriteLine($"A2: {v}"))
a2.Init();

NOTE: it also occurs with the same "lambda body" but different anonymous method:

Action<Vector3> m1 = v => Console.WriteLine($"A: {v}");
Action<Vector3> m2 = v => Console.WriteLine($"A: {v}");
// m1 differs from m2 because are compiled to two different
// anonymous methods, even if they have the same code
a.OnClick = m1;
a.Init();
// Here you are applying:
//   _init.OnClick -= a.OnClick; -> _init.OnClick [ ]
//   _init.OnClick  = a.OnClick; -> _init.OnClick [ m1 ]
// the subscriber is m1
a.OnClick = m2;
// But here you are applying:
//   _init.OnClick -= a.OnClick; -> _init.OnClick [ m1 ]
//   _init.OnClick  = a.OnClick; -> _init.OnClick [ m1, m2 ]
// the subscriber is m2 (!= m1)
a.Init();

Same applies if the _init changes from one instance of A to another (eg. is not a static member)

Take also a look to Unsubscribe - MDN.

  •  Tags:  
  • c#
  • Related