如何解决C# 委托事件不发送类实例作为参数供参考
我想在我的游戏管理器上触发一个包含已死亡玩家的一般事件。在播放器类中,我定义了一个无参数事件 OnDied。 GameManager 订阅每个玩家以触发包含死亡者的偶数。但主要问题是我无法在游戏后期取消订阅委托,例如。重新加载场景时,因为匿名委托。
public class GameManager : MonoBehaviour {
public delegate void OnPlayerDiedEvent(Player target); public event OnPlayerDiedEvent OnPlayerDied;
public List<Player> players;
private void Start(){
foreach(Player p in players){
p.OnDied += ()=> { if(OnPlayerDied != null) OnPlayerDied(p); };
}
}
}
我本可以定义玩家 OnDied 事件来获取自身 (= OnDied(this)),但这看起来不对,因为当你订阅一个事件时,你实际上有一个对类实例的引用(在这个情况下,GameManager 知道玩家), 就像这样:
public class Player : MonoBehaviour {
public delegate void OnDiedEvent(Player _this); public event OnDiedEvent OnDied;
public void Die(){ //call whener player should die
if(OnDied != null)
OnDied(this); //<-- is odd,better to use System.Action OnDied;
}
}
在 GameManager 中,我使用了一个包装方法 HandleOnPlayerDied(Player target)。
private void Start(){
foreach(Player p in players){
p.OnDied += HandleOnPlayerDied;
}
}
void HandleOnPlayerDied(Player target){
if(OnPlayerDied != null)
OnPlayerDied(target);
}
void OnDestroy(){
foreach(Player p in players){
p.OnDied -= HandleOnPlayerDied;
}
}
同样,这种方法有效,但我很不高兴将类实例作为其事件的参数,只是为了保留当前死亡玩家的引用。 一种非常愚蠢的方法是每帧检查所有玩家的活动状态并与他们之前的状态进行比较。然后是对死掉的玩家的引用,但是这违反了使用事件的原则,而且由于每帧检查效率低下。
解决方法
第一个
private void Start(){
foreach(Player p in players){
p.OnDied += ()=> { if(OnPlayerDied != null) OnPlayerDied(p); };
}
}
永远不会按预期工作。由于 lambda 表达式,您不能像您已经注意到的那样取消订阅,但更糟糕的是:由于 lambda 表达式中的变量捕获/闭包,您将使用分配给 p
的最后一个值调用所有事件!
现在你的第二种方法是好的。但正如您所说,您的经理类需要事先知道 Player
的每个实例。
像 static
这样的事件怎么样
(通常对于单参数事件,我更喜欢直接使用 Action
而不是自定义委托类型)
public class Player : MonoBehaviour
{
// Since you pass in the according reference anyway
// this event can as well simply be static right away
public static event Action<Player> OnDied;
public void Die()
{
OnDied?.Invoke(this);
}
}
和
public class GameManager : MonoBehaviour
{
public event Action<Player> OnPlayerDied;
private void Start()
{
Player.OnDied += HandlePlayerDied;
}
private void HandlePlayerDied(Player player)
{
OnPlayerDied?.Invoke(player);
}
private void OnDestroy()
{
Player.OnDied -= HandlePlayerDied;
}
}
通过这种方式,所有经理都需要知道有一个这种类型可以调用的事件,并且我会得到一个 Player
引用。
注意当然这个额外的GameManager
事件基本上是完全多余的,如果除了转发这个事件之外没有做任何其他事情。您也可以直接将所有侦听器附加到 Player.OnDied
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。