C#

5/1 TIL - Delegate, Func, Action, 람다

bugmin 2024. 5. 1. 23:56

오늘도 마찬가지로 팀 프로젝트에 집중한 하루였지만 그중에서 알아야할 거 같은 내용을 기록하였다.

 

Delegate

델리게이트는 메서드를 참조하는 타입이라 생각하면 된다. 변수처럼 메서드를 저장할 수가 있게 된다.

 

c++에선 함수 포인터라는 용어로 만나본 적이 있는 내용이다.

 

그냥 함수를 쓰면 되지 왜 델리게이트를 써야하냐면 팀프로젝트하다보면 접근이 힘든 상황이 생길 수도 있으니 메서드 여러 개를 연결해두고 쓰자는 취지다.

 

또한 UI면 UI부분만 다뤄야지 게임 진행의 로직까지 들어간다면 다소 복잡해질 수가 있다.

 

delegate void MyDelegate(string message);

static void Method1(string message)
{
    Console.WriteLine("Method1: " + message);
}

static void Method2(string message)
{
    Console.WriteLine("Method2: " + message);
}

class Program
{
    static void Main()
    {
        // 델리게이트 인스턴스 생성 및 메서드 등록
        MyDelegate myDelegate = Method1;
        myDelegate += Method2;

        // 델리게이트 호출
        myDelegate("Hello!");

        Console.ReadKey();
    }
}

 

델리게이트를 사용하는 예제이다.

 

델리게이트를 선언할때는 일반 메서드 선언하듯이 쓴다음에 앞에다가 delegate만 달아주면 된다.

 

선언한 델리게이트의 형식에 맞게 선언한 함수를 델리게이트 인스턴스 안에다가 넣어주면 된다.

추가적으로 더 많은 메서드를 넣고 싶으면 += 연산자로 넣어줄 수 있다.

 

이벤트 키워드

 

다만 델리게이트는 클래스 외부에서도 호출이 가능해 중요한 함수라면 이를 남용할 수 있는 보안적 문제가 있다.

고로 델리게이트를 선언한다음에 이를 한 번더 랩핑해주는, 감싸주는 event 키워드를 사용해 클래스 내부에서만 쓸 수 있도록 보안성을 높이고 캡슐화를 하는 것이다.

할당연산자 = 는 사용 못하고 +=, -= 연산자만 사용가능하다.

 

즉 외부 클래스에선 구독 신청, 취소는 가능하지만 델리게이트 호출을 할 수는 없다는 것이다.

// 적 클래스
public class Enemy
{
    // 델리게이트, 이벤트 선언
    public delegate void EnemyAttackHandler(float damage);
    public event EnemyAttackHandler OnAttack;

    // 적의 공격 메서드
    public void Attack(float damage)
    {
        // 이벤트 호출
        OnAttack?.Invoke(damage);
		// null 조건부 연산자
		// null 참조가 아닌 경우에만 멤버에 접근하거나 메서드를 호출
    }
}

// 플레이어 클래스
public class Player
{
    // 플레이어가 받은 데미지 처리 메서드
    public void HandleDamage(float damage)
    {
        // 플레이어의 체력 감소 등의 처리 로직
        Console.WriteLine("플레이어가 {0}의 데미지를 입었습니다.", damage);
    }
}

// 게임 실행
static void Main()
{
    // 적 객체 생성
    Enemy enemy = new Enemy();

    // 플레이어 객체 생성
    Player player = new Player();

    // 플레이어의 데미지 처리 메서드를 적의 공격 이벤트에 추가
    enemy.OnAttack += player.HandleDamage;

    // 적의 공격
    enemy.Attack(10.0f);
}

 

여기서 나오는 물음표(?)는 무엇이냐면 이벤트인 OnAttack 이 null 일 수도 있음을 알리는 것이다.

 

객체를 생성하고 그 안의 이벤트에 원하는 함수들을 += 연산자를  넣어준다.

 

Attack 함수서 이벤트를 호출하고 있으니 만일 이벤트가 있다면 Invoke(damage)를 통해 HandleDamage 함수를 수행할 것이고 파라미터로는 damage가 들어가 결과적으론 출력창에 데미지를 입었음을 출력하게 된다.

 

좀 더 쉽게 생각해보면

 

쿠팡과 인터넷 쇼핑몰에서 사고 싶은 상품이 품절됐다면 재입고 알림 기능이 있는데 이걸 걸어둔다 생각하면 된다.

 

만일 상품이 재입고가 됐다면 알림을 걸어둔 사람들에게 알려줄 것이다.

 

이렇듯 이벤트를 호출하게 되면 알림을 걸어둔 이벤트들을 다 수행을 하게 되는 것이다.

 

람다

 

람다는 익명의 메서드를 만드는 것으로 일회용품 같은 느낌으로 생각하면 좋다. 한 번 쓰고 말건데 굳이 메서드를 써야하나 싶은 상황서 간단하게 정의할 수가 있다.

 

(parameter_list) => expression

 

Calculate calc = (x, y) => 
{	
		return x + y;
};

Calculate calc = (x, y) => x + y;

 

원래 소괄호 안에 파라미터 중괄호 안에 코드부가 들어가지만

코드부가 한줄인 경우 그냥 if문서 그러하듯 한줄만 써도 된다.

 

이것이 바로 이름 없는 메서드인 람다라 할 수 있겠다.

 

델리게이트 변수를 만들고 이름 없는 람다로 만든 메서드를 전해준다면 이름이 없는 메서드라해도 델리게이트가 이를 참조하고 있기에 사용이 가능해진다. 간단한 처리를 위해선 람다를 던져주는 것이 편한 상황이 생기기도 한다.

 

Func과 Action

 

그냥 델리게이트에서 이벤트로 진화했다면 거기서 더 사용하기 쉽게 진화한 Func와 Action으로

 

델리게이트를 대체할 수 있는 미리 정의가 되어있는 제네릭 형식이다.

 

Func는 반환값이 있는 델리게이트, Action은 반환값이 없는 델리게이트로 생각하면 편하다.

 

// Func를 사용하여 두 개의 정수를 더하는 메서드
int Add(int x, int y)
{
    return x + y;
}

// Func를 이용한 메서드 호출
Func<int, int, int> addFunc = Add;
int result = addFunc(3, 5);
Console.WriteLine("결과: " + result);
// Action을 사용하여 문자열을 출력하는 메서드
void PrintMessage(string message)
{
    Console.WriteLine(message);
}

// Action을 이용한 메서드 호출
Action<string> printAction = PrintMessage;
printAction("Hello, World!");

 

Func과 Action의 사용예제를 각각 살펴보면 Func은 반환값을 가지고 있고 Action은 반환값이 없고 파라미터만 받고 있는 것을 알 수 있다.

 

결과적으론 그냥 Func과 Action을 사용하면 되지만 흐름을 알기위해 델리게이트부터 학습이 필요함을 느낄 수가 있다.

'C#' 카테고리의 다른 글

C# - ??, is, as 연산자와 패턴 일치 기법  (0) 2024.06.17
C# - Clamp 함수와 사용 예제  (1) 2024.05.27
C# 인터페이스와 열거형  (1) 2024.04.26
C# 고급 문법 및 기능  (0) 2024.04.25
C# OOP와 클래스  (0) 2024.04.24