- Home /
How do convert a lambda event subscription to be able to unsubscribe it?
I'm very new to events/delegates so sorry if I use the incorrect terminology.
I'm using an inventory script for Unity, that uses C# events/delegates to subscribe to a right click event on an item slot.
The problem is when I dynamically add new item slots, I need to add the event handlers to the new slots. If I just run UpdateEvents(), the ones that were there in the first place, now has duplicate triggers.
The current code is using a lambda syntax, and I've studied these threads on how to create a delegate instance:
Here's the original lambda subscription:
// This is the lambda expression that I want to unsubscribe to
ItemSlots[i].OnRightClickEvent += slot => EventHelper(slot, OnRightClickEvent);
Here's what I tried, and I marked with ** on the parts that my IDE highlights as wrong:
// Try 1
EventHandler lambda = slot => EventHelper(slot, OnRightClickEvent);
ItemSlots[i].OnRightClickEvent += lambda;
// Try 2
EventHandler handler = (sender, e) => EventHelper(sender, OnRightClickEvent);
ItemSlots[i].OnRightClickEvent += handler;
// Try 3
var myDelegate = delegate(sender, e) { EventHelper(**e**, OnRightClickEvent); };
ItemSlots[i].OnRightClickEvent += myDelegate;
I also tried converting it without using lambda, but it doesn't work like it should. I'm not sure what "slot" refers to in the lambda. Is it the instance triggering the event? Here's what didn't work, but didn't give any errors:
// Try without lambda
ItemSlots[i].OnRightClickEvent += OnRightClickEvent;
Here's a shortened version of the complete code. I don't fully understand how the EventHelper()-method works, but it seems to be some shortcut to check for null.
using System;
using System.Collections.Generic;
using UnityEngine;
public abstract class ItemContainer : MonoBehaviour, IItemContainer {
public List<ItemSlot> ItemSlots;
// There are really 8 event here, but I simplified it
public event Action<BaseItemSlot> OnRightClickEvent;
protected virtual void Awake() {
UpdateEvents();
SetStartingItems();
}
public virtual void UpdateEvents() {
for (int i = 0; i < ItemSlots.Count; i++) {
// This is the lambda expression that I want to unsubscribe to
ItemSlots[i].OnRightClickEvent += slot => EventHelper(slot, OnRightClickEvent);
}
}
private void EventHelper(BaseItemSlot itemSlot, Action<BaseItemSlot> action) {
if (action != null)
action(itemSlot);
}
}
Answer by Hellium · Mar 07, 2019 at 06:04 PM
Try this:
using System;
using System.Collections.Generic;
using UnityEngine;
public abstract class ItemContainer : MonoBehaviour, IItemContainer {
public List<ItemSlot> ItemSlots;
private List<Action<BaseItemSlot>> listeners = new List<Action<BaseItemSlot>>();
// There are really 8 event here, but I simplified it
public event Action<BaseItemSlot> OnRightClickEvent;
protected virtual void Awake()
{
UpdateEvents();
SetStartingItems();
}
public virtual void UpdateEvents()
{
for (int i = 0; i < ItemSlots.Count; i++)
{
Action<BaseItemSlot> listener = slot => EventHelper(slot, OnRightClickEvent);
if( listeners.Count <= i )
{
listeners.Add(listener);
}
else
{
ItemSlots[i].OnRightClickEvent -= listeners[i];
listeners[i] = listener;
}
ItemSlots[i].OnRightClickEvent += listeners[i];
}
}
public virtual void RemoveEvents()
{
for (int i = 0; i < ItemSlots.Count; i++)
ItemSlots[i].OnRightClickEvent -= listeners[i];
}
private void EventHelper(BaseItemSlot itemSlot, Action<BaseItemSlot> action)
{
if (action != null)
action(itemSlot);
}
}
Thanks a lot! This seems to be very close to what I needed. However when I run UpdateEvents() again, it adds another set of triggers. I noticed the RemoveEvents() method, but it wasn't in use, so I added it to the top of UpdateEvents(), however I got ArgumentOutOfRangeException: Argument is out of range.
I tried to put if (listeners.Count > 0 && listeners[i] != null) {
inside the for loop (with curly braces of course) but i got an "out of range" error also when adding a new item...
I have updated my answer so as to remove the listener if it has been added beforehand.
I'm considering storing the reference inside the ItemSlots[i] object. Not sure if that's a good idea though. :)
Thanks, this worked great, and was cleaner that to store the reference in the itemslot. However when I try to add back the other events, I run into a problem.
Should I store one List for each event, like private List> rightClickListeners = new List>(); private List> dragStartListeners = new List>();
...and so on?
It seems to be so much repeating of code. I accepted your answer though :)
I am sorry, I've tried something but I wasn't able to get a working and clean solution....
Good luck!
Answer by nicmarxp · Mar 08, 2019 at 09:11 AM
I ended up solving it another way, based on some tips I got from other forums, also on naming the events.
public event Action<BaseItemSlot> RightButtonClicked;
public virtual void UpdateEvents() {
for (int i = 0; i < ItemSlots.Count; i++) {
// Unsubscribe, subscribe again
ItemSlots[i].RightButtonClicked -= OnRightButtonClicked; ItemSlots[i].RightButtonClicked += OnRightButtonClicked;
}
}
private void EventHelper(BaseItemSlot itemSlot, Action<BaseItemSlot> action) {
if (action != null)
action(itemSlot);
}
private void OnRightButtonClicked(BaseItemSlot slot) {
EventHelper(slot, RightButtonClicked);
}
It seems there is no issue unsubscribing something that isn't subscribed, so I always unsubscribe it first. This way I don't have to keep a list of what's going on.
Thanks for your help :)
Your answer
Follow this Question
Related Questions
Delegates versus class references 1 Answer
Event to change level 1 Answer
Login form: Do I have to set custom delegates to null in OnDestroy? 0 Answers