람다

황현중·2025년 12월 9일

1. 람다 메서드를 위한 전용 델리게이트 관점

1-1. 람다는 결국 “메서드 조각”이다

람다는 한 줄로 말하면 이렇게 볼 수 있다.

람다식 = 어딘가에 넘겨줄 “작은 메서드 조각”을 코드 안에서 바로 적는 문법

그리고 이 “작은 메서드”를 담는 그릇이 바로 델리게이트 타입(delegate)이다.

// 전용 델리게이트 타입 선언
delegate bool NumberTest(int n);

class Program
{
    static void Main()
    {
        NumberTest isEven = n => n % 2 == 0;   // 람다를 델리게이트에 담음
        NumberTest isOdd  = n => n % 2 != 0;

        Console.WriteLine(isEven(10)); // true
        Console.WriteLine(isOdd(10));  // false
    }
}
  • delegate bool NumberTest(int n); → “int 하나 받아서 bool을 반환하는 함수를 담는 전용 타입”
  • n => n % 2 == 0 → 이 부분이 바로 “익명 메서드 본문” 역할을 하는 람다식
  • NumberTest isEven = ... → “이 델리게이트 변수는 이 람다 메서드를 가리킨다”는 뜻

정리하면, 람다 자체는 익명 메서드이고,
그걸 담는 타입이 델리게이트라고 보면 된다.


1-2. 전용 델리게이트를 만들어 두면 좋은 이유

어떤 공통 함수를 만들고, “조건”만 바꿔가며 쓰고 싶을 때 전용 델리게이트가 유용하다.

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 = { 1, 2, 3, 4, 5, 6 };

// 1) 일반 메서드 전달
static bool IsEven(int n) => n % 2 == 0;

int evenCount1 = Count(arr, IsEven);

// 2) 람다식으로 바로 조건 전달
int evenCount2 = Count(arr, n => n % 2 == 0);
int overThree  = Count(arr, n => n > 3);
  • Count는 “배열을 돌면서 조건에 맞는 것만 세는 공통 함수”
  • 어떤 조건으로 셀지는 NumberTest 델리게이트에 람다를 넣어 바꿔준다
  • 조건을 바꿔 끼우는 플러그인처럼 람다를 사용하는 느낌

이 패턴이 확장된 게 Predicate<T>, Comparison<T> 같은 델리게이트들이다.


1-3. 직접 만든 델리게이트 vs Func / Action / Predicate

매번 delegate ...를 선언하기 귀찮으니까, .NET에서는 미리 여러 델리게이트 타입을 만들어두었다.

  • Action, Action<T>, Action<T1,T2,...> → 반환값 없음
  • Func<T1,...,TResult> → 마지막 타입이 반환형
  • Predicate<T> → 실질적으로 Func<T,bool>과 같은 역할

예를 들어, 이런 전용 델리게이트를:

delegate bool NumberTest(int n);

굳이 안 만들고 이렇게도 쓸 수 있다:

Func<int, bool> test1 = n => n % 2 == 0;
Predicate<int> test2 = n => n > 10;

요약하면:

  • 람다를 쓰려면 **어딘가에 담겨야 하는데**, 그 그릇이 델리게이트 타입이다.
  • 직접 선언한 전용 델리게이트: delegate bool NumberTest(int n);
  • 미리 제공되는 전용 델리게이트: Func, Action, Predicate<T>
  • 람다 = 그 델리게이트에 들어가는 익명 메서드

2. 컬렉션과 람다 메서드 관점

두 번째 축은 컬렉션(List, 배열, LINQ)에서 람다를 어떻게 쓰는지이다.
실무에서는 이 부분에서 람다가 가장 많이 등장한다.

2-1. List<T> 메서드 + 람다

List<T>에는 델리게이트를 받는 메서드가 아주 많다.

  • Find(Predicate<T> match)
  • FindAll(Predicate<T> match)
  • Exists(Predicate<T> match)
  • RemoveAll(Predicate<T> match)
  • Sort(Comparison<T> comparison)
  • ForEach(Action<T> action)

모두 “델리게이트 인수”를 받기 때문에, 여기에 람다를 바로 넘겨줄 수 있다.

(1) Find / FindAll / Exists / RemoveAll 예제

var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// 1) Find: 조건을 만족하는 첫 번째 원소
int firstEven = list.Find(n => n % 2 == 0);

// 2) FindAll: 조건을 만족하는 모든 원소
List<int> evenList = list.FindAll(n => n % 2 == 0);

// 3) Exists: 조건을 만족하는 게 하나라도 있는지
bool hasBig = list.Exists(n => n > 100);

// 4) RemoveAll: 조건을 만족하는 원소 모두 삭제
int removedCount = list.RemoveAll(n => n % 2 == 0);
  • n => n % 2 == 0Predicate<int> 타입에 맞는 람다식
  • “이 숫자가 조건에 맞는지?”를 검사하는 작은 함수를 바로 인수로 넘긴 셈

(2) Sort + Comparison<T> 예제

var people = new List<(string Name, int Age)>
{
    ("Kim", 30),
    ("Lee", 20),
    ("Park", 40),
};

// 나이 기준 오름차순 정렬
people.Sort((a, b) => a.Age.CompareTo(b.Age));

// 이름 기준 내림차순 정렬
people.Sort((a, b) => string.Compare(b.Name, a.Name, StringComparison.Ordinal));

여기서 (a, b) => ... 부분이 바로 Comparison<T> 델리게이트에 전달되는 람다 메서드이다.


2-2. LINQ와 람다 – 컬렉션 처리의 핵심 조합

LINQ 확장 메서드들도 결국
“제네릭 메서드 + 델리게이트 인수 + 람다 전달” 구조이다.

대표적인 메서드들:

  • Where(Func<T, bool>)
  • Select(Func<TSource, TResult>)
  • Any(Func<T, bool>)
  • All(Func<T, bool>)
  • OrderBy(Func<T, TKey>)
  • GroupBy(Func<T, TKey>)

(1) Where + Select + ToList 간단 예제

var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

// 짝수만 고르고(Where), 제곱으로 바꾸고(Select), 리스트로 만들기(ToList)
var evenSquares = numbers
    .Where(n => n % 2 == 0)   // Func<int, bool>
    .Select(n => n * n)       // Func<int, int>
    .ToList();                // List<int>
  • Where 안의 n => n % 2 == 0Func<int,bool> 델리게이트에 들어가는 람다
  • Select 안의 n => n * nFunc<int,int> 델리게이트에 들어가는 람다

(2) 실무 스타일 예: 주문 목록 필터링

class Order
{
    public string CustomerCode { get; set; }
    public decimal Amount { get; set; }
    public bool IsCanceled { get; set; }
}

var orders = new List<Order>
{
    new Order { CustomerCode = "C001", Amount = 100_000m, IsCanceled = false },
    new Order { CustomerCode = "C001", Amount =  50_000m, IsCanceled = true  },
    new Order { CustomerCode = "C002", Amount = 200_000m, IsCanceled = false },
};

// 1) 취소되지 않은 주문만
var validOrders = orders
    .Where(o => !o.IsCanceled)
    .ToList();

// 2) 특정 거래처(C001) + 10만원 이상 주문만
var filtered = orders
    .Where(o => o.CustomerCode == "C001" && o.Amount >= 100_000m)
    .ToList();

// 3) 거래처별 매출 합계
var sumByCustomer = orders
    .Where(o => !o.IsCanceled)
    .GroupBy(o => o.CustomerCode)
    .Select(g => new
    {
        Customer = g.Key,
        Total = g.Sum(o => o.Amount)
    })
    .ToList();

여기 있는 모든 => 뒤의 코드들이 다 람다 메서드이고,
각각 Func<Order,bool>, Func<Order,string>, Func<Order,decimal> 등의 델리게이트 자리에 들어간다.


3. 두 관점을 한 번에 정리

3-1. [람다 메서드를 위한 전용 델리게이트] 관점

  • 람다식은 결국 델리게이트에 들어가는 익명 메서드다.
  • 델리게이트는 “이런 모양의 함수를 받을게”라고 선언하는 타입이다.
    • 직접 만든 전용 델리게이트: delegate bool NumberTest(int n);
    • 미리 제공된 델리게이트: Func, Action, Predicate<T>, Comparison<T>
  • 공통 함수(Count, Filter, Process 등)에
    “동작(조건/계산)을 나중에 결정하는 플러그인”으로 람다를 꽂는 느낌으로 쓰면 된다.

3-2. [컬렉션과 람다 메서드] 관점

  • List<T>Find, FindAll, Exists, Sort, RemoveAll, ForEach 등은 모두 델리게이트 인수를 받는다.
  • 여기에 람다를 넘겨서 “조건/정렬 기준/실행 동작”을 편하게 지정할 수 있다.
  • LINQ의 Where, Select, OrderBy, GroupBy 등은 제네릭 메서드 + Func 델리게이트 + 람다의 조합이다.
  • 실무에서 람다를 가장 많이 보는 곳은 결국 “컬렉션 + 람다 = 데이터 필터링/변환/집계” 영역이다.

이 두 관점을 머릿속에 같이 두면,

“람다 = 델리게이트에 들어가는 작은 메서드”
“컬렉션/ LINQ = 그 작은 메서드를 실전에서 쓰는 장소”

이렇게 연결돼서 훨씬 이해하기 쉬워진다.

0개의 댓글