오늘 수업 내용중 선생님의 코드를 실행했을시에 이상한 점이 하나 있었다.
일단 구성 코드는 아래와 같았다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIButtons : MonoBehaviour
{
public enum eBtnTypes
{
Yellow, Green, Blue
}
public Button[] arrBtns;
public System.Action<eBtnTypes> onClick;
private void Start()
{
for (int i = 0; i < arrBtns.Length; i++) {
this.arrBtns[i].onClick.AddListener(() => {
this.onClick((eBtnTypes)temp);
});
}
}
}
위 코드를 간략하게 설명해보면
Yello Green blue 라는 세개의 버튼 enum타입을 필드에 선언해 주었고 아래에는 버튼 타입으 배열과 위의 enum타입을 인자로 가지는 system. Action<T> 대리자 onClick 을 선언해 준 상태이다. (유니티 인스펙터 창에서 스크립트에 각각 별개의 버튼 오브젝트 세개를 할당했다.)
아래 start 매서드에는 for문을 돌며 버튼타입의 배열 길이 만큼 배열을 확인하는데 액션대리자를 사용해 각 인덱스의 요소(버튼 오브젝트들)의 onClick.AddListener 메서드의 인자로 위에 필드에 선언한 system. Action<T> 대리자 onClick을 넣어주고 system. Action<T> 대리자 onClick은 for문의 이니셜라이저 섹션의 i를 받아 enum타입으로 명시적 변환을 해주는 코드가 있다.
근데 여기서 문제가 발생했다.
게임뷰 화면에서 각각의 버튼을 클릭하니 이게 무슨 "3"을 콘솔 로그에 표시해주는게 아닌가?
원인을 검색하기 위해 검색어로 "반복문 대리자 사용" 을 검색해보았고 해답을 얻을수 있었다.
우선 클로저에 대해 알게 되었는데, 클로저(Closure)란 외부 변수나 필드와 같은 "환경"을 저장하고 있는 함수를 뜻한다.
여기서 람다식을 사용할때 외부 변수 혹은 필드 변수를 사용하는 경우에는 Closure 처리가 되게 되어있다.
하지만 위의 코드에서 반복문을 돌며 선언된 int i 는 클로저 처리가 되지 않고 for문을 다 돌고 난뒤의 최종 결과 값 3 (배열의 길이가 3이므로) 을 system. Action<T> 대리자 onClick이 참조하게 된것이다.
즉 반복문 안에서 람다식을 사용할때 외부 변수나 필드를 사용하려면 (클로저 처리 되게 하려면) 단순 값이 아니라 같은 문 안에서 클로저 처리가 된 변수 자체를 참조하여야 한다는것이다.
이를 좀더 쉽게 풀어쓰자면 람다문 안에서 람다문 또는 식에서 외부의 변수를 사용하려면 클로저 처리를 해주어야 한다는 것이다.
이건 정말 단순하게도 for문 안에서 i를 새로운 변수에 넣어 초기화 시켜주는걸로 해결할수 있었다.
이렇게 하면 매번 초기화 되는 변수 temp를 참조하게 된다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIButtons : MonoBehaviour
{
public enum eBtnTypes
{
Yellow, Green, Blue
}
public Button[] arrBtns;
public System.Action<eBtnTypes> onClick;
private void Start()
{
for (int i = 0; i < arrBtns.Length; i++) {
int temp = i;
this.arrBtns[i].onClick.AddListener(() => {
this.onClick((eBtnTypes)temp);
});
}
}
}
이제 버튼들이 문제 없이 작동하는걸 볼수 있다!
PS. 이건 클로저랑 관련 없는 내용이긴하지만 AddListner 매서드에 대해 검색하다 굉장히 맘에 드는 코드 한줄을 발견했다.
GameObject.activeSelf 는 해당 오브젝트의 활성화 상태를 부울값으로 반환하는 프로퍼티인데 해당 프로퍼티 앞에 논리 부정 연산자 ! 를 붙임으로 한줄로 깔끔하게 메서드를 실행할때마다 active 상태를 활성화 할수 있는게 정말 깔끔하고 유용할듯 하다. (해당 코드를 보기전에는 if문을 이용하여 엑티브 상태를 체크하고 활성화 해주었었다.)
조사에 도움을 주신 블로그들 : Link
[C# 문법] C# Closure : 반복문 내 람다식 사용 방법
안녕하세요. 오늘은 C# 문법에서 클로저(Closure) 에 대해서 알아보려고 합니다. 참조 https://www.csharpstudy.com/DevNote/Article/26 https://doublsb.tistory.com/73 클로저(Closure)란? 클로저(Closure)란, 외부 변수나 필
afsdzvcx123.tistory.com
C# Closure 이해하기 - C# 프로그래밍 배우기 (Learn C# Programming)
C# Closure 이해하기 [제목] C# Closure 이해하기 Closure란 무엇인가? C#에서 Closure는 어떻게 구현되는가? C#에서 Closure는 어떤 곳에 사용되는가? 이 아티클은 이러한 질문에 대한 답변을 정리한 글이다.
www.csharpstudy.com
'유니티 엔진 클라이언트 > 수업 과제' 카테고리의 다른 글
[유니티] Setting 창 제작 (클립보드 복사 플러그인) (0) | 2023.02.19 |
---|---|
[유니티] 상점 탭메뉴 스크롤 연동 (0) | 2023.02.19 |
[유니티] 종스크롤 2D 스페이스 슈터 ver1.1 (0) | 2023.02.11 |
[유니티] 오브젝트 풀링이란 무엇인가? + 구현 (0) | 2023.02.05 |
[유니티] 코루틴 & 스레드 (0) | 2023.02.01 |