Unity關(guān)于for循環(huán)匿名委托傳參的一些坑(閉包陷阱)

/**
 *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);
        }
    }
}

這個腳本掛在畫布上 創(chuàng)建一個Btn拖到屏幕上方的位置失活


image.png

然后我們點擊會把I傳遞給Text
點一輪都是5


image.png

因為他一開始幫你注冊了事件 但是Btn里面點擊激活并沒有,在你點的時候循環(huán)已經(jīng)完畢,一直是最大值,在點擊的時候就所有Btn只會被傳遞最大值的i,但是不是從0開始最后不應(yīng)該是4停止嗎,因為for循環(huán)底層是
 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++是最后執(zhí)行的也就是說最后i++還是執(zhí)行了。
至于i++和++i for循環(huán)結(jié)果是沒區(qū)別的 詳細的可以看
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

正常了,原因可能是每次循環(huán)都會New出一個新的iCache,而注冊Btn里面的iCache是指向外面每次for循環(huán)產(chǎn)生的與他們同一次循環(huán)產(chǎn)生的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提取到外部一直覆蓋為什么現(xiàn)在都成四了,因為結(jié)束時i++執(zhí)行后就沒有再進入循環(huán)所以iCache是不會多增加一次

然后用類傳入i呢,結(jié)果正常應(yīng)該是傳入的時候自動幫你新建了一個臨時變量儲存起來,類似于之前的 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

因為這個坑讓我很多靠順序生成讀表的東西都迷之錯誤,然后在這里探究下以防以后出現(xiàn)再傻掉了

之后無意間看到其實這個叫閉包陷阱
這個是概念
內(nèi)層的函數(shù)可以引用包含在它外層的函數(shù)的變量,即使外層函數(shù)的執(zhí)行已經(jīng)終止。但該變量提供的值并非變量創(chuàng)建時的值,而是在父函數(shù)范圍內(nèi)的最終值。
用臨時變量接收相當于每次new出來這個對象
詳細的
https://www.cnblogs.com/jiejie_peng/p/3701070.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容