어렵게 생각할 필요 없이, 딱 이렇게 기억하면 된다.
람다(람다식) = 이름 없는 작은 함수를 간단하게 표현한 문법
예를 들어, 이 한 줄:
x => x * 2
의미를 풀어 보면,
x (정수라고 가정)x * 2 (두 배)즉 “정수 하나 받아서 두 배로 만들어 돌려주는 함수”를 아주 짧게 쓴 것이다.
(매개변수들) => 식
(매개변수들) => { 문장들; }
예를 들면:
x => x + 1 // 매개변수 1개, 식 1줄
(a, b) => a + b // 매개변수 2개, 식 1줄
n => { return n * n; } // 블록(중괄호) 사용, 여러 줄 가능
x => x * 2(a, b) => a + breturn 없이 식만 쓰면 됨: n => n * n{ } 블록 안에 일반 메서드처럼 작성핵심은 이 한 문장이다.
람다식은 결국 델리게이트(또는 Func/Action)에 들어가는 “익명 함수”를 표현하는 문법
기존 코드를 단계별로 보면서 비교해보자.
delegate bool NumberTest(int n);
static bool IsEven(int n)
{
return n % 2 == 0;
}
NumberTest test = IsEven;
NumberTest test = delegate(int n)
{
return n % 2 == 0;
};
NumberTest test = n => n % 2 == 0;
또는 사용자 정의 delegate 대신 Func를 쓰면:
Func<int, bool> test = n => n % 2 == 0;
이 세 가지는 모두 “정수 하나 받아서 짝수인지 검사하는 함수”를 표현하는 서로 다른 문법일 뿐이다.
람다식은 그 중 가장 짧고 읽기 쉬운 표현이라고 보면 된다.
using System;
class Program
{
static void Main()
{
// int 두 개 받아서 int를 반환하는 람다: 더하기
Func<int, int, int> add = (a, b) => a + b;
// int 두 개 받아서 int를 반환하는 람다: 빼기
Func<int, int, int> sub = (a, b) => a - b;
Console.WriteLine(add(3, 5)); // 8
Console.WriteLine(sub(10, 3)); // 7
}
}
add와 sub는 둘 다 “함수를 담고 있는 변수”다.
add(3, 5)처럼 호출하면, 내부에 들어있는 람다식이 실행된다.
먼저 델리게이트 버전부터 보자.
delegate bool NumberTest(int n);
static int Count(int[] numbers, NumberTest test)
{
int count = 0;
foreach (int n in numbers)
{
if (test(n))
count++;
}
return count;
}
이걸 람다식으로 호출하면 이렇게 된다.
int[] arr = { 3, 5, 4, 2, 6, 7, 8, 11, 13, 15, 20 };
int evenCount = Count(arr, n => n % 2 == 0); // 짝수 개수
int oddCount = Count(arr, n => n % 2 != 0); // 홀수 개수
int gtTen = Count(arr, n => n > 10); // 10보다 큰 수 개수
n => n % 2 == 0 이 부분이 바로 람다식NumberTest 타입 델리게이트에 들어간다test(n)을 호출하면, 실제로 이 람다식이 실행된다static int Count(int[] numbers, Func<int, bool> test)
{
int count = 0;
foreach (int n in numbers)
{
if (test(n))
count++;
}
return count;
}
int evenCount = Count(arr, n => n % 2 == 0);
이 경우 n => n % 2 == 0의 타입은 Func<int, bool>이고,
Count는 이를 이용해서 유연하게 조건을 바꾸며 사용할 수 있다.
람다식이 가장 많이 쓰이는 곳이 바로 LINQ다.
Where, Select, OrderBy 같은 LINQ 메서드들은 대부분 내부적으로 Func 델리게이트를 받는다.
using System;
using System.Linq;
class Program
{
static void Main()
{
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 짝수만 고르기
var evens = numbers
.Where(n => n % 2 == 0) // 이 람다식의 타입은 Func<int, bool>
.ToList();
Console.WriteLine("짝수들:");
foreach (var n in evens)
Console.WriteLine(" " + n);
}
}
Where의 개념적인 시그니처는 대략 다음과 같다.
IEnumerable<T> Where(Func<T, bool> predicate)
즉 n => n % 2 == 0은 “T가 int일 때의 Func<int, bool>”이고,
Where는 이 함수를 사용해서 요소들을 하나씩 걸러낸다.
var squares = numbers
.Select(n => n * n) // Func<int, int> 람다: n을 n*n으로 변환
.ToArray();
문자열 예제도 가능하다.
string[] names = { "Kim", "Park", "Lee", "Choi" };
var nameLengths = names
.Select(name => new { Name = name, Length = name.Length })
.ToList();
var desc = numbers
.OrderByDescending(n => n) // Func<int, int> : 정렬 기준을 n 자신으로 사용
.ToList();
객체 리스트에서도 마찬가지로 사용할 수 있다.
var ordered = products
.OrderBy(p => p.Category) // 카테고리 순
.ThenBy(p => p.Price); // 같은 카테고리 안에서 가격 오름차순
반환값이 필요 없는 경우에는 Action과 람다를 많이 쓴다.
using System;
class Program
{
static void Main()
{
// 매개변수 없는 Action
Action hello = () => Console.WriteLine("Hello!");
// string 하나 받는 Action
Action<string> print = msg =>
{
Console.WriteLine("메시지: " + msg);
};
hello();
print("람다 공부 중");
}
}
이벤트 핸들러에서도 자주 보게 되는 패턴이다.
button1.Click += (sender, e) =>
{
MessageBox.Show("버튼 클릭!");
};
여기서 (sender, e) => { ... } 도 결국 “object, EventArgs를 받아서 void인 람다식”이다.
람다식에는 크게 두 가지 형태가 있다.
=> 오른쪽이 식 한 줄인 경우.
n => n * n
(a, b) => a + b
s => s.Length
=> 오른쪽이 { } 블록으로 여러 줄인 경우.
n =>
{
int r = n * n;
Console.WriteLine(r);
return r;
};
실제로는 둘 다 같은 “람다식”이고, 코드 길이가 짧으면 식 람다, 길어지면 문 람다를 쓰는 정도의 차이다.