Unity关于for循环匿名委托传参的一些坑(闭包陷阱)

/**
 *Copyright(C) 2019 by #COMPANY#
 *All rights reserved.
 *FileName:     #SCRIPTFULLNAME#
 *Author:       #AUTHOR#
 *Version:      #VERSION#
 *UnityVersion:#UNITYVERSION#
 *Date:         #DATE#
 *Description:   
 *History:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Text1 : MonoBehaviour
{

    public Transform btn;
    List<int> list = new List<int> {
        1,2,3,4,5
    };
    // Use this for initialization
    void Start()
    {
        for (int i = 0; i < list.Count; i++)
        {
            Transform go = Instantiate(btn);
            go.transform.position = btn.transform.position + Vector3.down * i*30;
            go.gameObject.SetActive(true);
            go.GetComponent<Button>().onClick.AddListener(() => {
                go.Find("Text").GetComponent<Text>().text = i.ToString();
            });
            go.SetParent(transform);
        }
    }
}

这个脚本挂在画布上 创建一个Btn拖到屏幕上方的位置失活


image.png

然后我们点击会把I传递给Text
点一轮都是5


image.png

因为他一开始帮你注册了事件 但是Btn里面点击激活并没有,在你点的时候循环已经完毕,一直是最大值,在点击的时候就所有Btn只会被传递最大值的i,但是不是从0开始最后不应该是4停止吗,因为for循环底层是
 public void For(ref int i,int maxCount,Action func)
    {      
        while (i<maxCount)
        {            
            func();
            i++;
        }
    }
 int j = 0;
        For(ref j, 4, () => { Debug.Log(j); });

image.png

他的i++是最后执行的也就是说最后i++还是执行了。
至于i++和++i for循环结果是没区别的 详细的可以看
https://blog.csdn.net/tsvico/article/details/74943281

/**
 *Copyright(C) 2019 by #COMPANY#
 *All rights reserved.
 *FileName:     #SCRIPTFULLNAME#
 *Author:       #AUTHOR#
 *Version:      #VERSION#
 *UnityVersion:#UNITYVERSION#
 *Date:         #DATE#
 *Description:   
 *History:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Text1 : MonoBehaviour
{

    public Transform btn;
    List<int> list = new List<int> {
        1,2,3,4,5
    };
    // Use this for initialization
    void Start()
    {
        for (int i = 0; i < list.Count; i++)
        {
            Transform go = Instantiate(btn);
            go.transform.position = btn.transform.position + Vector3.down * i*30;
            go.gameObject.SetActive(true);
            var iCache = i;
            go.GetComponent<Button>().onClick.AddListener(() => {
                go.Find("Text").GetComponent<Text>().text = iCache.ToString();
            });
            go.SetParent(transform);
        }
    }
}

然后如果用临时变量在注册Btn之前存一下呢


image.png

正常了,原因可能是每次循环都会New出一个新的iCache,而注册Btn里面的iCache是指向外面每次for循环产生的与他们同一次循环产生的iCache

image.png
/**
 *Copyright(C) 2019 by #COMPANY#
 *All rights reserved.
 *FileName:     #SCRIPTFULLNAME#
 *Author:       #AUTHOR#
 *Version:      #VERSION#
 *UnityVersion:#UNITYVERSION#
 *Date:         #DATE#
 *Description:   
 *History:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Text1 : MonoBehaviour
{

    public Transform btn;
    List<int> list = new List<int> {
        1,2,3,4,5
    };
    // Use this for initialization
    void Start()
    {
        int iCache;
        for (int i = 0; i < list.Count; i++)
        {
            Transform go = Instantiate(btn);
            go.transform.position = btn.transform.position + Vector3.down * i*30;
            go.gameObject.SetActive(true);
            iCache = i;
            go.GetComponent<Button>().onClick.AddListener(() => {
                go.Find("Text").GetComponent<Text>().text = iCache.ToString();
            });
            go.SetParent(transform);
        }
    }
}

然后把iCache提取到外部一直覆盖为什么现在都成四了,因为结束时i++执行后就没有再进入循环所以iCache是不会多增加一次

然后用类传入i呢,结果正常应该是传入的时候自动帮你新建了一个临时变量储存起来,类似于之前的 var iCache=i;的操作

using System;
/**
*Copyright(C) 2019 by #COMPANY#
*All rights reserved.
*FileName:     #SCRIPTFULLNAME#
*Author:       #AUTHOR#
*Version:      #VERSION#
*UnityVersion:#UNITYVERSION#
*Date:         #DATE#
*Description:   
*History:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Text1 : MonoBehaviour
{

    public Transform btn;
    List<int> list = new List<int> {
        1,2,3,4,5
    };
    // Use this for initialization
    void Start()
    {
        for (int i = 0; i < list.Count; i++)
        {
            CreatBtn(i);
        }
        //for (int k = 0; k < 4; ++k)
        //{
        //    Debug.Log(k);
        //}
        //int j = 0;
        //For(ref j, 4, () => { Debug.Log(j); });
    }

    public void CreatBtn(int i)
    {
        Transform go = Instantiate(btn);
        go.transform.position = btn.transform.position + Vector3.down * i * 30;
        go.gameObject.SetActive(true);
        go.GetComponent<Button>().onClick.AddListener(() =>
        {
            go.Find("Text").GetComponent<Text>().text = i.ToString();
        });
        go.SetParent(transform);
    }

    public void For(ref int i, int maxCount, Action func)
    {
        while (i < maxCount)
        {
            func();
            i++;
        }
    }
}
image.png

因为这个坑让我很多靠顺序生成读表的东西都迷之错误,然后在这里探究下以防以后出现再傻掉了

之后无意间看到其实这个叫闭包陷阱
这个是概念
内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。但该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
用临时变量接收相当于每次new出来这个对象
详细的
https://www.cnblogs.com/jiejie_peng/p/3701070.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容