如何转换lambda事件订阅以使其能够退订?

时间:2019-03-07 17:23:57

标签: c# unity3d events delegates

我对事件/代理人非常陌生,如果使用不正确的术语,请见谅。

我正在为Unity使用清单脚本,该脚本使用C#事件/代理来订阅项目位置上的右键单击事件。

问题是当我动态添加新项目插槽时,我需要将事件处理程序添加到新插槽中。如果我只运行UpdateEvents(),那么最初存在的事件现在具有重复的触发器。

当前代码使用的是lambda语法,我已经研究了这些线程如何创建委托实例:

这是原始的lambda订阅:

// This is the lambda expression that I want to unsubscribe to
ItemSlots[i].OnRightClickEvent += slot => EventHelper(slot, OnRightClickEvent);

这是我尝试过的方法,我在IDE突出显示的部分上用**标记为错误:

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

我也尝试了在不使用lambda的情况下进行转换,但是它并不像应该的那样工作。我不确定lambda中的“插槽”是指什么。是实例触发事件吗?这是行不通的,但是没有给出任何错误:

// Try without lambda
ItemSlots[i].OnRightClickEvent      += OnRightClickEvent;

这是完整代码的简化版本。我不完全了解EventHelper()方法的工作原理,但这似乎是检查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);
    }
}

2 个答案:

答案 0 :(得分:1)

让我们逐步进行:

// This is the lambda expression that I want to unsubscribe to
ItemSlots[i].OnRightClickEvent += slot => EventHelper(slot, OnRightClickEvent);

在左侧有一个事件(OnRightClickEvent)。 右侧的问题是C#和.NET开发人员在简化难以理解的代码语法方面走得太远了。基本上,您可以扩展到:

ItemSlots[i].OnRightClickEvent += (slot) =>  { EventHelper(slot, OnRightClickEvent); };
    ItemSlots[i].OnRightClickEvent += delegate(slot){ EventHelper(slot, OnRightClickEvent);};

都是一样的。如果仍然很困难:

private void ItemSlot_OnRightClickEvent(BaseItemSlot slot)
{
     EventHelper(slot, OnRightClickEvent);
}    

然后您分配:

ItemSlots[i].OnRightClickEvent += ItemSlot_OnRightClickEvent;

slot是将来自调用事件的ItemSlots对象的参数。 最好也删除监听器:

ItemSlots[i].OnRightClickEvent -= ItemSlot_OnRightClickEvent;

使用匿名方法,由于您没有指向该方法的指针,因此无法删除该方法。而且由于它是事件,因此只能擦除所属类(在此情况下为ItemSlot)的整个事件。

我认为最好保持简洁并使用旧语法。例如,当您开始编写时,Visual Studio将使用tab自动提供正确的方法签名以自动完成:

 ObjectName.EventName += [tab]

Visual Studio将生成一个名为ObjectName_EventName的方法,该方法具有正确的返回值和参数。

答案 1 :(得分:0)

带有C#事件的通常模式是这样的:

    public event Action SomethingHappened;
    void OnSomethingHappened()
    {
        if (SomethingHappened!= null) SomethingHappened();
    }

    public void DoSomething()
    {
        // Actual logic here, then notify listeners
        OnSomethingHappened();
    }

您首先声明事件,通常使用过去时来命名。 然后是用于触发事件的私有方法,通常使用On前缀命名(如果派生类需要触发该事件,则也可以保护该方法)。 最后,是一种公共方法,供外部调用者进行操作,并在其中通知监听者触发该事件。

您可以将此事件同时应用于ItemSlotItemContainer类:

    public event Action<BaseItemSlot> RightButtonClicked;
    void OnRightButtonClicked(BaseItemSlot item)
    {
        if (RightButtonClicked != null) RightButtonClicked(item);
    }

然后,每次ItemContainer.OnRightButtonClicked触发时,您需要调用ItemSlot.RightButtonClicked

因此,您可以通过管道传递事件:

    void OnEnable()
    {
        for (int i = 0; i < ItemSlots.Count; i++)
        {
            ItemSlots[i].RightButtonClicked += OnRightButtonClicked;
        }
    }
    void OnDisable()
    {
        for (int i = 0; i < ItemSlots.Count; i++)
        {
            ItemSlots[i].RightButtonClicked -= OnRightButtonClicked;
        }
    }