안녕하세요. 박민성입니다. 🤗
오늘은 C#에 람다 식(Lambda Expression)에 대해서 글을 쓰고자 합니다. 🤔
저는 람다식을 평소 Unity 개발 해올때 많이 써왔습니다.
많이 써왔지만, 그냥 "이렇게 쓰는구나~"하고 사용해 왔는데요.
람다 식에 대해서 자세하고 정확히 알기 위해서 람다 식에 대해서 글을 쓰고자 합니다.
1. 람다식이란?
람다식은 이름이 없는 함수를 간결하게 표현하는 문법입니다.
2. 람다식의 기본 구조
매개변수 => 실행할 코드
// 예시
number => number * 2
여기서 =>이 람다 연산자입니다.
만약에 매개변수가 없다면
() => Debug.Log("실행");
이렇게 () 괄호를 적고 람다식을 써주면 됩니다.
만약에 매개변수가 여러 개면?
어떻게 할까~~~요?
(a, b) => a + b
이런 식으로 괄호 안에 콤마를 직고 매개변수를 적으면 됩니다.
당연히 위에서 본 매개변수 하나 들어간 버전도
(number) => number * 2
이런 식으로 괄호를 적어도 됩니다.
3. 표현식 람다와 문 람다
람다를 찾아보다가 람다에도 종류가 있는걸 봤습니다.
람다에는 표현식 람다와 문 람다가 있습니다.
두가지의 차이점은
한 줄로 실행하는 것이 표현식 람다라고 하고여
number => number * 2 // 표현식 람다
여러 줄을 실행하는 것을 문 람다라고 합니다.
number =>
{
int result = number * 2;
Debug.Log(result);
return result;
}
문 람다를 사용하려면 람다식을 쓰고 중괄호를 쓰고 식들을 적으면 됩니다.
여기서 주의할 점은 표현식 람다는 오른쪽 식의 결과가 자동으로 반환되지만,
문 람다는 반환값이 필요하면 return을 무조건 적어야지 반환할 수 있습니다.
4. 델리게이트와의 관계
델리게이트는 대리자로 함수를 담아서 한꺼번에 부를 수 있는 친구입니다.
내장으로 있는 델리게이트 종류는 Action, Func, Predicate이 있습니다.
여기서 람다식은 무명"함수"이기 때문에 델리게이트에 종류에 담아서
변수 선언하여 여러 곳에서 사용할 수 있는데여
Func<int, int> doubleValue = number => number * 2;
여기서
var doubleValue = number => number * 2; // XXXXXX
이런 식으로 타입 추론(var)를 사용해서 선언하는 것이 안되는데요.
왜냐하면 컴파일러가 람다식의 대상 타입을 알 수 없기 때문입니다. (처음 알았습니다 헉)
5. Unity 활용 예제
button.onClick.AddListener(() => OpenPanel());
버튼 이벤트에 함수를 AddListener시킬 수 있는데요.(여기서 AddListener는 버튼 이벤트인 onClick에 발생 되었을 때 발생되었다는 것을 알려주는? onClick이 되면 AddListener한 함수 실행됨)
SkinDataSO skin = skinList.Find(skin => skin.skinId == targetId);
이렇게 리스트 검색을 할때도 유용하게 씁니다. (Find안에는 Predicate<T> 이 들어갑니다.)
6. 외부 변수 캡처와 클로저
람다식 밖의 변수를 내부에서 사용할 수 있습니다.
int damage = 10;
Action attack = () =>
{
enemy.TakeDamage(damage);
};
이때 람다가 외부의 damage 변수를 사용하는 것을 변수 캡처라고 합니다.
변수 캡처란 익명 함수가 바깥 변수를 참조하면 해당 변수가 캡처된 것입니다.
그렇기 때문에 간단한 코드 예제입니다.
int damage = 10;
Action attack = () => Debug.Log(damage);
damage = 30;
attack(); // 30
attack을 선언할때 damage는 10입니다. 하지만 damage 값을 바꾸면
attack을 호출했을 때 그 결과 값이 달라지는데요.
보통은 변수 선언할 때 그 값을 복사합니다.
int damage = 10;
int damage2 = damage
damage = 30;
damage2 // 10
하지만 람다 생성 시에 그 값을 복사하는 것이 아닌 그 변수를 갭처함으로서 변수를 공유합니다.
이것이 람다식 변수 캡처입니다.
여기서 클로저가 안나올 수가 없는데요.
클로저란 람다식과 그 람다가 캡처한 외부 변수들을 함께 유지하는 구조입니다.
컴파일러는 개념적으로 다음 코드가 있을 때
int damage = 10;
Action attack = () => Debug.Log(damage);
캡처한 변수를 보관할 숨겨진 객체를 만들어서 처리합니다.
class GeneratedClosure
{
public int damage;
public void Attack()
{
Debug.Log(damage);
}
}
이런식으로?
여기서. 함수 안에서 사용하는 것이 아닌 클래스 필드에 있다면????
private int damage = 10;
private void Start()
{
Action attack = () => Debug.Log(damage);
}
이 경우에는 람다가 damage를 캡처한 것이 아닌 현재 객체에 this를 통해서 필드를 접근합니다.
여기서 중요한점!!🌟🌟🌟🌟🌟
만약에 위에처럼 함수 안에서 damage를 사용해서 변수 캡처가 일어나면
그 캡처한 람다를 담는 클로저 객체가 생기는데요!!
반복적으로 이 클로저 객체를 생성하게 된다면 이 클로저는 class이기 때문에 메모리 할당 발생합니다.
그러면 GC가 처리해야 하기 때문에 반복적인(예: Update())에서 새 람다를 만드는 것은 피하는 게 좋습니다.
private void Update()
{
int damage = 10;
Action attack = () => enemy.TakeDamage(damage);
attack();
} // X X X X X X X X X X X X X X X X X
이상으로 람다 식에 대해서 알아보았는데요~!!!!
오늘은 찾아보고 또 찾아보면서 모르는 내용이 계속 나와서 머리가 아팠습니다. 🤕
하지만 정말 신기하고 새로운 지식들이 머리에 들어와서 정말 재밌고 알찬 하루가 되었던 것 같습니다. ☺️
내일은 더욱 알차게 보내고 작성해보도록 하겠습니다.
봐주셔서 감사합니다. ☺️