【问题标题】:C# lambda call in for loop references to last objectC# lambda 调用 for 循环引用最后一个对象
【发布时间】:2015-05-25 10:28:43
【问题描述】:

我已经研究过这个问题,并尝试按照之前几个问题 (such as this one) 中的说明修复代码,但它仍然无法正常工作。虽然我必须说我检查的所有答案都是从 2009 年到 2010 年,所以它们可能已经过时了。

这是罪魁祸首代码:

            foreach(Entity player in players)
            {
                if(player.actions.Count > 0)
                {
                    Entity temp = player;

                    player.isDoingAction = true;
                    Debug.Log(player.name + " started action");

                    player.actions.Dequeue().Execute(() => { temp.isDoingAction = false; Debug.Log(temp.name + " finished"); });
                }
            }

这将打印以下内容:

Player1 started action
Player2 started action
Player2 finished
Player2 finished

何时打印:

Player1 started action
Player2 started action
Player1 finished
Player2 finished

或类似的东西。

此代码在 Unity 协程函数中运行。

代码稍大一点:

GameManager.cs

private IEnumerator RunTurn()
{
    ...
    ...
    ...

    for(int i = 0; i < phases; i++)
    {
        //Assign action to each player
        foreach(Entity player in players)
        {
            if(player.actions.Count > 0)
            {
                Entity temp = player;

                player.isDoingAction = true;
                Debug.Log(player.name + " started action");
                player.actions.Dequeue().Execute(() => { temp.isDoingAction = false; Debug.Log(temp.name + " finished"); });
            }
        }

        //Wait for each player to finish action
        foreach(Entity player in players)
        {
            while(player.isDoingAction == true)
            {
                Debug.Log("Waiting for " + player.name);
                yield return null;
            }
        }
    }

    ...
    ...
    ...
}

Action.cs

public override void Execute(System.Action callback)
{
    Move(callback);             
}

private void Move(System.Action callback)
{
    ...
    ...
    ...

    //Move target entity
    target.MoveToPosition(newPosition, mSpeed, callback);
    target.location = newLocation;

    ...
    ...
    ...
}

Entity.cs

public void MoveToPosition(Vector3 position, float speed, System.Action callback)
{
    StartCoroutine(CoMoveToPosition(position, speed, callback));
}

//Move to position
private IEnumerator CoMoveToPosition(Vector3 position, float speed, System.Action callback)
{
    while(position != transform.position)
    {
        transform.position = Vector3.MoveTowards(transform.position, position, speed * Time.deltaTime);
        yield return null;
    }

    //Move finished so use callback
    callback();
}

解决方案

事实证明 Unity 中存在协程和匿名 lambda 回调的错误。查看this链接了解更多信息。

工作代码:

foreach(Entity player in players)
{
    if(player.actions.Count > 0)
    {
        player.isDoingAction = true;
        Debug.Log(player.name + " started action");

        System.Func<Entity, System.Action> action = new System.Func<Entity,System.Action>(p =>
        new System.Action(() => { p.isDoingAction = false; Debug.Log(p.name + " finished"); }));

        player.actions.Dequeue().Execute(action(player));
    }
}

【问题讨论】:

  • 有些事情没有意义。使用本地temp 是“捕获”(闭包样式)变量并防止它在下一次迭代中更改的标准方法。代码中还有其他我们没有看到的内容吗?
  • @Amit 我在整个循环结构中进行了编辑。这真的没有意义,这就是我在这里发布这个的原因。
  • 如果您捕获名称本身会发生什么(我认为这不是您真正想要的,但可能有助于理解)?:string tempName = player.name; ... Debug.Log(tempName...
  • 试试这个:Action a = () =&gt; Debug.Log(temp.name); a(); player.actions.Dequeue().Execute(a); 这个想法是在进入 Execute 调用之前测试 lambda 是否捕获了temp.name,以及之后是否以某种方式发生了变化。
  • 毫无意义:-)。看起来你的回调被覆盖了,问题不在于捕获或 lambda。最终验证:Action a = (player.name == 'player1') ? (() =&gt; Debug.Log("Player1")) : (() =&gt; Debug.Log("not Player1"));。不再使用捕获。如果失败,那肯定是回调被重置了。

标签: c# lambda


【解决方案1】:

您可以通过以下方式捕获该值:

var action = new Func<Entity, Action>(p => 
new Action(() => { p.isDoingAction = false; Debug.Log(p.name + " finished")); })(player);
player.actions.Dequeue().Execute(action);

【讨论】:

  • @Dzienny - 除了提供答案之外,您是否能够解释 OP 方法的问题以及解决方案如何避免该问题?
【解决方案2】:

试试

player.actions.Dequeue().Execute(temp => 
{ temp.isDoingAction = false; 
  Debug.Log(temp.name + " finished"); 
});

【讨论】:

  • 这不是原始代码试图做的。 temp 已更改,不再保留对 player 的引用。
  • 该方法被定义为void Execute(System.Action callback),所以我认为这行不通?我在想是否制作一个以实体为参数的委托是否可行,但我还没有尝试过。我也想尽可能地保持解决方案的通用性。
猜你喜欢
  • 2016-03-03
相关资源
最近更新 更多