我对事件/代理人非常陌生,如果使用不正确的术语,请见谅。
我正在为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);
}
}
答案 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
前缀命名(如果派生类需要触发该事件,则也可以保护该方法)。
最后,是一种公共方法,供外部调用者进行操作,并在其中通知监听者触发该事件。
您可以将此事件同时应用于ItemSlot
和ItemContainer
类:
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;
}
}