CodeGym /행동 /C# SELF /람다식과 델리게이트 결합하기

람다식과 델리게이트 결합하기

C# SELF
레벨 50 , 레슨 4
사용 가능

1. 도입

델리게이트는 함수의 “시그니처”를 설명하는 타입이야: 어떤 매개변수를 받고, 무엇을 반환하는지. 함수의 여권이나 코드용 클럽 출입증 같아: 시그니처가 맞으면 통과, 안 맞으면 못 들어와.

예제:

public delegate int Operation(int a, int b);

이 델리게이트는 두 개의 int를 받아 int를 반환하는 함수를 의미해. 람다가 도입되기 전엔 이런 식이었지(신선하긴 한데 그다지 편하진 않아):

Operation add = delegate (int x, int y)
{
    return x + y;
};

람다가 도입되면서 코드는 더 간결해졌어:

Operation add = (x, y) => x + y;

재미있는 사실: C#에서 람다식은 그냥 “익명 함수”를 만드는 문법일 뿐이고, 적절한 타입의 델리게이트 인스턴스로 자동 컴파일돼. 그래서 둘의 경계가 거의 구분이 안 돼.

내장 델리게이트: Func, Action, Predicate

모든 경우마다 직접 델리게이트를 만들지 않으려고, C#에는 범용 타입들이 있어:

  • Func<T1, T2, ..., TResult> — 매개변수(T1, T2, ...)를 받고 TResult 타입을 반환하는 범용 델리게이트.
  • Action<T1, T2, ...> — 거의 같은데 반환값이 없어서 (void).
  • Predicate<T> — 하나의 매개변수를 받고 bool을 반환하는 범용 델리게이트(필터에 자주 씀).
델리게이트 반환 타입 문법 예시
Func<int, int>
int
Func<int, int> f = x => x + 1;
Action<string>
void
Action<string> a = s => ...;
Predicate<double>
bool
Predicate<double> p = d => ...;

예제:

Func<int, int, int> add = (a, b) => a + b;
Action<string> show = s => Console.WriteLine(s);
Predicate<int> isEven = n => n % 2 == 0;

2. 델리게이트로 감싼 람다식

델리게이트를 받는 메서드에 람다 전달하기

델리게이트를 파라미터로 받는 어떤 메서드든, 그 시그니처에 맞는 람다를 넘길 수 있어.

public static void CalculateAndShow(int a, int b, Func<int, int, int> operation)
{
    int result = operation(a, b);
    Console.WriteLine($"결과: {result}");
}

// 람다로 호출하는 예
CalculateAndShow(5, 7, (x, y) => x * y); // 결과: 35

Action 예제

void ProcessString(string message, Action<string> processor)
{
    processor(message);
}

// 람다 사용 예
ProcessString("안녕, 세상!", s => Console.WriteLine(s.ToUpper()));

Predicate 예제

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = numbers.FindAll(n => n % 2 == 0);

이럴 때면 직접 델리게이트를 만들 필요가 있나 싶지 — 이미 검증된 Microsoft 표준이 있잖아.

델리게이트와 람다가 함께 동작하는 방식

flowchart TD
    A[람다식] -->|생성| B((델리게이트))
    B -->|인수로 전달됨| C[메서드]
    B -->|저장됨| D[속성/필드/컬렉션]
    C -->|호출됨| E[람다 코드]

3. 자신의 메서드에 델리게이트로서 람다 전달하기

자신의 메서드가 델리게이트를 사용하면, 람다 덕분에 사용성이 훨씬 유연해져.

예제: 우리 미니 계산기

// 연산을 다루는 메서드 정의
static int PerformOperation(int a, int b, Func<int, int, int> operation)
{
    return operation(a, b);
}

// 다양한 연산 적용 (람다 사용)
int sum = PerformOperation(3, 4, (x, y) => x + y);
int multiply = PerformOperation(3, 4, (x, y) => x * y);
int max = PerformOperation(3, 4, (x, y) => x > y ? x : y);

Console.WriteLine(sum);      // 7
Console.WriteLine(multiply); // 12
Console.WriteLine(max);      // 4

세 개의 별도 메서드 대신 람다로 로직을 바꾸니 훨씬 우아하지?

4. LINQ에서의 람다 + 델리게이트 (그리고 그 외)

LINQ의 메서드들은 '작은 논리 함수'를 기대하니까, 사실상 델리게이트를 받는 형태야. 거의 모든 표준 LINQ 메서드는 이런 타입을 받지:

  • Func<T, bool> — 필터링용 (Where)
  • Func<T, TResult> — 변환용 (Select)
  • Func<T, TKey> — 정렬용 (OrderBy)
List<string> names = new List<string> { "안나", "올레그", "빅토르", "야나" };

// 길이가 3을 초과하는 이름만 가져오기
var longNames = names.Where(name => name.Length > 3);

// 이름을 대문자로 변환
var upperNames = names.Select(name => name.ToUpper());

LINQ는 '람다의 극대화된 버전' 같은 거야 — 전달할 수 있는 건 다 전달할 수 있어.

5. 결합: 델리게이트-람다 저장과 전달

함수 목록

람다는 일회성 익명 함수가 아니야. 컬렉션에 람다들을 저장해두고 동적으로 사용할 수 있어.

List<Func<int, int, int>> operations = new List<Func<int, int, int>>
{
    (x, y) => x + y,
    (x, y) => x - y,
    (x, y) => x * y,
    (x, y) => x / y
};

foreach (var op in operations)
{
    Console.WriteLine(op(10, 2));
}

테스트, 핸들러, 플러그인 패턴에서 자주 쓰이는 기법이지.

델리게이트 딕셔너리

플러그 앤 플레이 구조를 좋아한다면:

var mathFuncs = new Dictionary<string, Func<int, int, int>>
{
    { "plus", (x, y) => x + y },
    { "minus", (x, y) => x - y },
    { "pow", (x, y) => (int)Math.Pow(x, y) }
};

string command = "pow"; // 사용자 입력 흉내

if (mathFuncs.TryGetValue(command, out var operation))
{
    Console.WriteLine(operation(2, 5)); // 32
}
else
{
    Console.WriteLine("명령을 찾을 수 없어");
}

6. 유용한 팁

람다-델리게이트: 메서드에서 델리게이트 반환하기

메서드에서 델리게이트(즉, 람다)를 반환할 수 있어 — 사실상 함수 팩토리야!

Func<int, int> GetMultiplier(int factor)
{
    // factor로 곱하는 람다 델리게이트를 반환함
    return x => x * factor;
}

var triple = GetMultiplier(3);
Console.WriteLine(triple(5)); // 15

var quadruple = GetMultiplier(4);
Console.WriteLine(quadruple(5)); // 20

이렇게 클로저(closure)가 장난감이 아니라 실무 도구가 되지!

함수 합성: Combine, Delegate.Combine 및 멀티델리게이트

때론 하나의 호출로 여러 함수를 실행하고 싶을 때가 있어. 예를 들어 GUI 이벤트 처리: 버튼 클릭하면 여러 핸들러가 반응하잖아.

Action과 델리게이트는 여러 함수를 "이어붙이는" 걸 허용해:

Action<string> pipeline = s => Console.WriteLine("단계 1: " + s);
pipeline += s => Console.WriteLine("단계 2: " + s.ToUpper());
pipeline += s => Console.WriteLine("단계 3: " + s.Length);

pipeline("테스트");
// 출력:
// 단계 1: 테스트
// 단계 2: 테스트(대문자화)
// 단계 3: 4

체이닝이나 이벤트 처리 시 유용해.

중요: 값을 반환하는 함수들에서는 최종 결과가 마지막 호출의 반환값만 돌아와 — 초보자에게 놀라운 점일 수 있어. Action은 반환이 void라서 모든 핸들러가 실행돼.

람다-델리게이트를 callback 파라미터로 사용하기

고전적인 패턴 — “끝나면 호출해줘”.

void DownloadFile(string url, Action<string> onFinish)
{
    // 파일 다운로드 시뮬레이션...
    System.Threading.Thread.Sleep(500); // (긴 작업 흉내, 실제 GUI 앱에선 사용하지 마)
    onFinish($"'{url}' 다운로드가 완료되었어!");
}

DownloadFile("http://example.com", msg => Console.WriteLine(msg));

실제 앱에서는 (API, DB, 파일 작업 등) 표준 패턴이지.

고차함수 예: predicate로 필터 만들기

람다와 델리게이트 덕분에 다른 함수를 파라미터로 받는 범용 함수를 쓸 수 있어. 이게 바로 고차함수 패턴의 핵심.

List<int> Filter(List<int> source, Predicate<int> condition)
{
    List<int> result = new List<int>();
    foreach (var item in source)
        if (condition(item)) result.Add(item);
    return result;
}

// 다양한 람다 사용:
var onlyPositive = Filter(new List<int> { -2, 0, 2, 7 }, x => x > 0);
var onlyEven = Filter(new List<int> { -2, 0, 2, 7 }, x => x % 2 == 0);

람다 팩토리도 만들 수 있어:

Func<int, Predicate<int>> GetRangeChecker(int min, int max) =>
    x => x >= min && x <= max;

Predicate<int> inRange = GetRangeChecker(1, 10);

var filtered = Filter(new List<int> { 0, 5, 10, 15 }, inRange); // 5, 10

7. 흔한 실수와 결합의 뉘앙스

파라미터 시그니처가 맞지 않으면 컴파일러가 불평해 (CS1660: "Cannot convert lambda expression").

람다는 의외로 변수를 “기억”할 수 있고, 그 변수가 나중에 바뀌면 놀랄 수 있어(변수 캡처(closure) 주의).

반환값이 있는 멀티델리게이트는 마지막 핸들러의 결과만 반환하니 당황하지 않게.

주의: Expression<Func<int,bool>> 같은 곳에는 statement-람다가 아니라 꼭 expression-람다가 필요해.

2
과제
C# SELF, 레벨 50, 레슨 4
잠금
Func와 Action 사용하기
Func와 Action 사용하기
1
설문조사/퀴즈
델리게이트 - 람다식용 타입, 레벨 50, 레슨 4
사용 불가능
델리게이트 - 람다식용 타입
람다식과 델리게이트의 상관관계
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION