람다는 이름 없이 그 자리에서 만드는 메서드다
Esc

    람다는 이름 없이 그 자리에서 만드는 메서드다

    –C#기초

    앞 글에서 조건을 넘겼습니다.

    bool IsEven(int n) { return n % 2 == 0; }
    
    Count(nums, IsEven);

    IsEven 은 이 한 줄에서만 씁니다. 그런데 이름을 짓고 클래스 어딘가에 메서드로 만들어야 했습니다. 조건이 열 개면 메서드가 열 개 생기고, 각각 어디 있는지 찾아다녀야 합니다.

    한 번 쓰고 말 동작이면 이름을 붙이지 않고 그 자리에 적으면 됩니다.

    람다는 이렇게 생겼다

    Count(nums, n => n % 2 == 0);

    n => n % 2 == 0 이 람다입니다. 세 부분입니다.

    n => n % 2 == 0 받을 것. 타입은 안 적어도 된다 이 앞을 받아서 이 뒤를 한다 할 일. 결과가 그대로 돌아간다
    매개변수, 화살표, 본문. 이름도 반환 타입도 없다.

    메서드와 비교하면 지운 것이 많습니다.

    bool IsEven(int n) { return n % 2 == 0; }
            n => n % 2 == 0
    • 이름이 없습니다. 그 자리에서 쓰고 버리니 부를 일이 없습니다.
    • 반환 타입이 없습니다. 본문을 보면 bool 인 것이 뻔합니다.
    • 매개변수 타입이 없습니다. Func<int, bool> 자리에 넣었으니 int 인 것도 뻔합니다.
    • return 과 중괄호가 없습니다. 한 줄이면 그 결과가 그대로 돌아갑니다.

    컴파일러가 알아낼 수 있는 것을 전부 지운 것입니다. 새 문법이 아니라 짧게 적는 방법입니다.

    모양 몇 가지

    n => n * 2                    // 하나 받기
    (a, b) => a + b               // 둘 이상은 괄호로 묶습니다
    () => Console.WriteLine("안녕")  // 안 받으면 빈 괄호
    
    n =>                          // 여러 줄이면 중괄호와 return
    {
        int r = n * 2;
        return r + 1;
    }

    한 줄이면 return 이 필요 없고, 중괄호를 열면 return 을 적어야 합니다. 헷갈리기 쉬운데 규칙은 단순합니다. 중괄호를 열면 메서드처럼 적는다 입니다.

    담을 때도 씁니다.

    Func<int, int> twice = n => n * 2;
    Console.WriteLine(twice(5));      // 10

    바깥 변수를 데리고 다닌다

    람다에는 특이한 성질이 하나 있습니다.

    int limit = 10;
    
    Func<int, bool> over = n => n > limit;
    
    Console.WriteLine(over(15));    // True

    limit 은 람다 밖에 있는 변수인데 안에서 씁니다. 람다는 만들어질 때 주변에 있던 변수를 같이 챙겨 갑니다.

    그래서 이런 것도 됩니다.

    Func<int, bool> MakeOver(int limit)
    {
        return n => n > limit;
    }
    
    var over10 = MakeOver(10);
    var over100 = MakeOver(100);

    MakeOver 는 이미 끝났는데 over10 은 limit 이 10인 것을 기억하고 있습니다. 같은 코드로 서로 다르게 동작하는 것을 여러 개 찍어낸 셈입니다.

    앞에서 지역 변수는 메서드가 끝나면 사라진다고 했습니다. 람다가 데려간 변수는 예외입니다. 람다가 살아 있는 동안 같이 살아남습니다. 컴파일러가 그 변수를 힙으로 옮겨 두기 때문입니다.

    편한데 함정도 있습니다. 값이 아니라 변수를 데려갑니다.

    int n = 1;
    Action print = () => Console.WriteLine(n);
    
    n = 99;
    print();      // 1 이 아니라 99

    만들 때의 값이 아니라 부를 때의 값이 나옵니다. 반복문 안에서 람다를 만들어 모아 두면 이것 때문에 놀라는 일이 생깁니다.

    컬렉션에 넘기면 반복문이 사라진다

    List 에는 람다를 받는 메서드가 여럿 있습니다.

    var nums = new List<int> { 5, -3, 12, 8, -1 };
    
    nums.RemoveAll(n => n < 0);            // 음수 전부 삭제
    var found = nums.Find(n => n > 10);    // 조건에 맞는 첫 번째
    bool any = nums.Exists(n => n > 100);  // 하나라도 있는가
    nums.Sort((a, b) => b - a);            // 내림차순으로 정렬

    using System.Linq; 를 붙이면 더 많아집니다.

    var big = nums.Where(n => n > 5);              // 조건에 맞는 것만
    var names = dogs.Select(d => d.Name);          // 원하는 것만 뽑기
    int sum = nums.Sum();
    var sorted = dogs.OrderBy(d => d.Age);         // 나이순 정렬

    전부 어떻게 반복할지가 아니라 무엇을 할지만 적습니다. 반복문이 사라진 것이 아니라 안으로 들어간 것입니다.

    var result = new List<int>();
    foreach (var n in nums) if (n > 5) result.Add(n);
    var result = nums.Where(n => n > 5).ToList();

    두 코드가 같은 일을 합니다. 아래쪽은 조건 하나만 적혀 있어서 읽을 것이 적습니다.

    다만 짧다고 항상 나은 것은 아닙니다. 길게 이어 붙이면 오히려 읽기 어려워지고, 반복문 하나면 될 일에 여러 번 도는 경우도 생깁니다.

    정리

    • 람다는 이름 없이 그 자리에서 만드는 메서드입니다.
    • 매개변수, 화살표, 본문 셋뿐입니다. 알아낼 수 있는 것은 전부 생략합니다.
    • 한 줄이면 return 이 없고, 중괄호를 열면 메서드처럼 적습니다.
    • 바깥 변수를 데려갑니다. 메서드가 끝나도 그 변수는 살아남습니다.
    • 값이 아니라 변수를 데려갑니다. 부를 때의 값이 나옵니다.
    • 컬렉션 메서드에 넘기면 반복문 대신 조건만 적게 됩니다.
    • 짧다고 늘 나은 것은 아닙니다. 길게 이어지면 반복문이 읽기 쉬울 때도 있습니다.