C# 중급 문법 (8) : 익명 메서드와 람다식

안녕하세요! 지난 포스팅에서는 델리게이트이벤트를 통해 메서드를 변수처럼 다루고, 객체 간에 유연하게 통신하는 방법을 알아봤습니다. 이번에는 델리게이트와 이벤트를 사용할 때 코드를 훨씬 더 간결하고 직관적으로 만들어주는 두 가지 강력한 기능, **익명 메서드(Anonymous Method)**와 **람다식(Lambda Expression)**에 대해 자세히 살펴보겠습니다. 이 둘은 코드를 "즉석에서" 정의하고 전달할 수 있게 해줍니다.


1. 익명 메서드(Anonymous Method): 이름 없는 함수 

일반적으로 델리게이트나 이벤트에 할당할 메서드는 별도의 이름을 가진 메서드로 정의해야 했습니다. 하지만 익명 메서드이름 없이 바로 그 자리에서 정의하여 델리게이트에 할당할 수 있는 코드 블록입니다. C# 2.0에서 도입되어 코드를 더 간결하게 만들었습니다.

익명 메서드의 특징:

  • delegate 키워드 사용: 익명 메서드를 정의할 때 delegate 키워드를 사용합니다.
  • 이름 없음: 말 그대로 메서드 이름이 없습니다.
  • 즉석 정의: 델리게이트 변수에 할당하거나 이벤트에 등록할 때 바로 그 자리에서 코드를 작성합니다.
  • 외부 변수 캡처(Closure): 익명 메서드가 정의된 스코프(영역) 내의 지역 변수들을 사용할 수 있습니다.

익명 메서드 사용 예시:

// 델리게이트 정의 (이전 예시와 동일)
public delegate void MessageDelegate(string message);

public class Notifier
{
    public event MessageDelegate OnNotify;

    public void DoSomethingAndNotify(string data)
    {
        Console.WriteLine($"Notifier: 어떤 작업을 수행 중... (데이터: {data})");
        // 이벤트 발생
        OnNotify?.Invoke($"작업이 완료되었습니다: {data}");
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        Notifier notifier = new Notifier();

        // 1. 익명 메서드를 사용하여 이벤트 구독
        notifier.OnNotify += delegate(string msg) // delegate 키워드로 익명 메서드 시작
        {
            Console.WriteLine($"[익명 메서드] 알림 받음: {msg}");
        };

        // 외부 변수 캡처 예시
        string prefix = "LOG: ";
        notifier.OnNotify += delegate(string msg)
        {
            Console.WriteLine($"{prefix}{msg}"); // 외부 변수 prefix 사용
        };

        notifier.DoSomethingAndNotify("핵심 데이터");
        /*
        출력:
        Notifier: 어떤 작업을 수행 중... (데이터: 핵심 데이터)
        [익명 메서드] 알림 받음: 작업이 완료되었습니다: 핵심 데이터
        LOG: 작업이 완료되었습니다: 핵심 데이터
        */

        Console.WriteLine("\n--- 익명 메서드를 델리게이트 변수에 할당 ---");
        // 델리게이트 변수에 익명 메서드 할당
        MessageDelegate myLogger = delegate(string logMsg)
        {
            Console.WriteLine($"[MyLogger] {logMsg}");
        };

        myLogger("이 메시지는 익명 메서드를 통해 출력됩니다."); // [MyLogger] 이 메시지는 익명 메서드를 통해 출력됩니다.
    }
}

익명 메서드는 짧고 간단한 로직을 바로 그 자리에서 구현할 때 유용하며, 별도의 이름 있는 메서드를 만들 필요가 없어 코드의 가독성을 높일 수 있습니다.


2. 람다식(Lambda Expression): 익명 메서드의 진화형! 

람다식은 C# 3.0에서 LINQ(Language Integrated Query)와 함께 도입된 기능으로, 익명 메서드보다 훨씬 더 간결하고 표현력이 풍부한 문법을 제공합니다. 람다식은 '수학의 람다 계산'에서 영감을 받아, 입력과 출력을 정의하는 '수학 함수'처럼 코드를 표현합니다.

람다식의 핵심 문법: => (go-to 연산자)

람다식은 다음과 같은 형태로 작성됩니다:

([매개변수]) => [식 또는 문장 블록]

  • ( ): 매개변수 목록. 매개변수가 하나이면 괄호 생략 가능. 매개변수가 없으면 빈 괄호 () 사용.
  • =>: 'go-to' 연산자 또는 '람다 연산자'라고 불립니다. '입력'을 '출력/실행'으로 연결합니다.
  • [식 또는 문장 블록]: 람다식이 실행할 코드.
    • 식 본문 (Expression Body): 한 줄짜리 표현식인 경우. (예: a + b)
    • 문장 블록 (Statement Body): 여러 줄의 코드가 필요한 경우 { } 블록 사용.

람다식 사용 예시:

위의 Notifier 예시를 람다식으로 바꿔보겠습니다.

// 델리게이트 정의 (이전과 동일)
public delegate void MessageDelegate(string message);

public class Notifier
{
    public event MessageDelegate OnNotify;

    public void DoSomethingAndNotify(string data)
    {
        Console.WriteLine($"Notifier: 어떤 작업을 수행 중... (데이터: {data})");
        OnNotify?.Invoke($"작업이 완료되었습니다: {data}");
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        Notifier notifier = new Notifier();

        // 1. 람다식을 사용하여 이벤트 구독 (가장 일반적인 형태)
        // 매개변수가 하나이므로 괄호 생략 가능: msg =>
        notifier.OnNotify += (msg) => Console.WriteLine($"[람다식 1] 알림 받음: {msg}");

        // 람다식에서 여러 줄의 코드가 필요한 경우 블록 사용
        string suffix = "!!!";
        notifier.OnNotify += (msg) => // 외부 변수 suffix 캡처
        {
            Console.WriteLine($"[람다식 2] {msg}{suffix}");
            Console.WriteLine("    - 두 번째 람다 블록 실행");
        };

        notifier.DoSomethingAndNotify("핵심 데이터");
        /*
        출력:
        Notifier: 어떤 작업을 수행 중... (데이터: 핵심 데이터)
        [람다식 1] 알림 받음: 작업이 완료되었습니다: 핵심 데이터
        [람다식 2] 작업이 완료되었습니다: 핵심 데이터!!!
            - 두 번째 람다 블록 실행
        */

        Console.WriteLine("\n--- 람다식을 델리게이트 변수에 할당 (다양한 형태) ---");

        // 매개변수가 없는 람다식
        Action helloWorld = () => Console.WriteLine("Hello, World from Lambda!");
        helloWorld();

        // 매개변수가 여러 개인 람다식
        Func<int, int, int> addFunc = (x, y) => x + y; // int 두 개 받아 int 반환
        Console.WriteLine($"람다 덧셈: {addFunc(7, 3)}"); // 10

        // 반환형이 없는 람다식
        Action<string> greetAction = name => Console.WriteLine($"안녕하세요, {name}님!");
        greetAction("김철수");

        // 복잡한 로직을 가진 람다식 (문장 블록)
        Func<int, int, string> compareNumbers = (num1, num2) =>
        {
            if (num1 > num2) return $"{num1}이(가) {num2}보다 큽니다.";
            else if (num1 < num2) return $"{num1}이(가) {num2}보다 작습니다.";
            else return $"{num1}과 {num2}는 같습니다.";
        };
        Console.WriteLine(compareNumbers(10, 5)); // 10이(가) 5보다 큽니다.
    }
}

람다식은 익명 메서드보다 훨씬 더 간결한 문법을 제공하여, LINQ 쿼리나 이벤트 핸들러 등 짧은 코드 블록을 전달해야 할 때 코드를 매우 깔끔하게 만들어줍니다.


3. 익명 메서드 vs 람다식: 누가 더 좋을까? 

  • 익명 메서드 (delegate { ... }):
    • C# 2.0에서 도입.
    • 람다식보다 문법이 더 길지만, 내부적으로는 거의 동일하게 작동합니다.
    • 주로 레거시 코드나, 람다식이 너무 복잡해져서 가독성이 떨어질 때 (드물게) 사용될 수 있습니다.
  • 람다식 (=>):
    • C# 3.0에서 도입. 현대 C# 프로그래밍에서 압도적으로 선호됩니다.
    • 훨씬 간결한 문법로 코드의 가독성을 높입니다.
    • Func<...>와 Action<...> 같은 제네릭 델리게이트와 완벽하게 어울립니다.
    • LINQ 쿼리 표현식의 핵심입니다.

결론적으로, 새로운 코드를 작성할 때는 특별한 이유가 없는 한 람다식을 사용하는 것이 좋습니다. 람다식이 제공하는 간결성과 유연성은 현대 C# 개발에서 생산성을 크게 향상시킵니다.


4. 유니티에서 익명 메서드/람다식 활용하기 

유니티는 이벤트 기반 시스템이 많기 때문에, 델리게이트와 람다식은 매우 중요하게 활용됩니다.


4.1. UI 이벤트 핸들링

유니티 UI (UGUI)의 버튼 클릭 이벤트 등에 람다식을 바로 연결할 수 있습니다.

C#
 
// ButtonHandler.cs
using UnityEngine;
using UnityEngine.UI; // UI 컴포넌트를 사용하기 위해 필요

public class ButtonHandler : MonoBehaviour
{
    public Button myButton;
    public Text statusText;

    void Start()
    {
        if (myButton != null)
        {
            // 람다식을 사용하여 버튼 클릭 이벤트에 함수 연결
            myButton.onClick.AddListener(() => { // 매개변수가 없으므로 () =>
                statusText.text = "버튼이 클릭되었습니다!";
                Debug.Log("버튼 클릭!");
                PlayClickSound(); // 다른 메서드 호출도 가능
            });

            // 매개변수가 있는 슬라이더 값 변경 이벤트 (예시)
            // Slider slider;
            // slider.onValueChanged.AddListener(value => { // 매개변수가 하나이므로 value =>
            //     Debug.Log("슬라이더 값: " + value);
            // });
        }
    }

    void PlayClickSound()
    {
        // 실제 게임에서는 AudioSource.Play() 등을 호출
        Debug.Log("클릭 사운드 재생!");
    }
}

따로 OnClickButton() 같은 메서드를 정의하지 않고, 버튼에 기능을 바로 연결할 수 있어 코드가 깔끔해집니다.


4.2. 콜백 함수 전달

비동기 작업(예: 네트워크 통신, 파일 로드 완료) 후에 실행될 콜백 함수를 람다식으로 전달하는 것이 일반적입니다.

C#
 
// DataLoader.cs
using UnityEngine;
using System; // Action, Func을 위해 필요

public class DataLoader : MonoBehaviour
{
    // 데이터를 로드하는 척 하고 콜백을 호출하는 메서드
    public void LoadDataFromServer(string url, Action<string> onDataLoaded) // Action<string>은 string 매개변수 하나, 반환값 없는 델리게이트
    {
        Debug.Log($"서버에서 데이터 로드 시작: {url}");
        // 실제로는 비동기 통신 로직
        // ... (가상의 딜레이)
        string loadedData = $"<데이터 from {url}> 성공적으로 로드됨!";
        onDataLoaded?.Invoke(loadedData); // 데이터 로드 완료 후 콜백 호출
    }

    void Start()
    {
        // 람다식을 사용하여 데이터 로드 완료 시 실행될 콜백 정의
        LoadDataFromServer("http://mygame.com/data", (data) => {
            Debug.Log($"[메인 스크립트] 로드된 데이터: {data}");
            // 로드된 데이터로 UI 업데이트 또는 게임 로직 처리
            if (data.Contains("성공"))
            {
                Debug.Log("데이터 로드 성공! 게임 시작 준비 완료.");
            }
        });
    }
}

이처럼 Action이나 Func 델리게이트를 매개변수로 받아 람다식을 전달하면, 특정 작업이 완료된 후 수행할 로직을 매우 유연하게 정의할 수 있습니다.


마무리하며: 람다식으로 C# 코드를 더욱 우아하게! 

익명 메서드람다식은 델리게이트와 이벤트를 활용할 때 코드를 더 간결하고 직관적으로 만들어주는 강력한 문법입니다. 특히 람다식은 현대 C# 개발에서 델리게이트와 함께 가장 많이 사용되는 기능 중 하나이며, LINQ, 비동기 프로그래밍, 유니티 UI 이벤트 등 다양한 분야에서 코드의 표현력과 생산성을 크게 향상시킵니다.

이 두 가지를 잘 활용하면, 여러분의 C# 코드가 더욱 우아하고 관리하기 쉬워질 것입니다.

익명 메서드나 람다식에 대해 더 궁금한 점이 있으시거나, 특정 유니티 상황에서의 활용법에 대해 알고 싶으신가요? 언제든지 질문해주세요!