본문 바로가기

코딩/C# 정리

[C#] 이벤트, 대리자(delegate) 내용 정리

대리자(delegate)

 

말 그대로 대리자이다. 즉, 대리로 함수를 실행 해 주는 것이다.

 

간단한 예시를 들어 보자. 

 

class Product
{
    public string Name { get; set; }
    public int Price { get; set; }

}

간단하게 물품 - 가격 정보가 들어 있는 클래스를 만들어 준다.

 

프로퍼티 형태로 만들어 주었다.

 

이제, 리스트를 만들어 주어 Product 아이템들을 넣어 준 다음, 정렬을 해 보도록 하자.

 

static void Main(string[] args)
{
    List<Product> products = new List<Product>()
    {
        new Product() { Name = "닌텐도4 스위치", Price = 350000 },
        new Product() { Name = "Ps5", Price = 560000 },
        new Product() { Name = "XBox", Price = 480000 }
    }; // 리스트에 처음 값을 정의하듯이 List<int> list = new List<int>{1,2,3); 한 것이다. 원소가 클래스가 들어가니 일일히 정의 해 준 것


    Console.WriteLine("정렬 전\n=====");
    foreach (var item in products)
    {
        Console.WriteLine(item.Name + "의 가격은 " + item.Price + "원 입니다.");
    }

    Console.WriteLine("\n");
    // 정렬
    products.Sort();

    Console.WriteLine("정렬 후\n=====");
    foreach (var item in products)
    {
        Console.WriteLine(item.Name + "의 가격은 " + item.Price + "원 입니다.");
    }

}

 

생겨난 오류

클래스 안에 있는 프로퍼티 값을 통해 비교해야 하기 때문에 그냥 Sort를 사용하면 위와 같이 오류가 생기게 된다.

 

따라서 Sort 함수 속에 클래스 속에 있는 어느 요소를 가지고 비교 할 것인지 체크하는 익명 대리함수를 만들어 주어야 한다.


코드를 아래와 같이 바꾸어 준다.

static void Main(string[] args)
{
    List<Product> products = new List<Product>()
    {
        new Product() { Name = "닌텐도4 스위치", Price = 350000 },
        new Product() { Name = "Ps5", Price = 560000 },
        new Product() { Name = "XBox", Price = 480000 }
    }; // 리스트에 처음 값을 정의하듯이 List<int> list = new List<int>{1,2,3); 한 것이다. 원소가 클래스가 들어가니 일일히 정의 해 준 것


    Console.WriteLine("정렬 전\n=====");
    foreach (var item in products)
    {
        Console.WriteLine(item.Name + "의 가격은 " + item.Price + "원 입니다.");
    }

    Console.WriteLine("\n");
    // 정렬
    products.Sort(delegate(Product a, Product b)
    {
        return a.Price.CompareTo(b.Price); // a의 Price 프로퍼티와 b의 Price 프로퍼티를 비교해서 정렬해라!
    });

    Console.WriteLine("정렬 후\n=====");
    foreach (var item in products)
    {
        Console.WriteLine(item.Name + "의 가격은 " + item.Price + "원 입니다.");
    }

}

Sort 속에 delegate 함수를 넣어 주고 두 개의 원소를 비교하는데, Product 클래스의 Price 프로덕트를 비교하여 정렬 하라고 설정 해 주는 것이다.

 

이렇게 설정 해 주게 되면

 

아래와 같이

Price 비교 정렬 결과

가격 값에 따라서 오름차순으로 정렬이 된다.

 


유니티에서의 간단한 활용

위와 같이 리스트에서 활용할 수도 있으며, 대리자는 함수 자체를 대리로 대신 수행하게 할 수도 있다.

 

한 delegate 별로 하나의 함수만을 수행 하는 것이 아닌, 여러 개의 함수를 체인처럼 연쇄적으로 등록하여 여러 개의 함수를 순서대로 수행하게 할 수도 있다.

 

유니티에서 살펴 보도록 하자

 

테스트 코드를 만들고, 부모와 연결 된 OnInIt()에 아래와 같이 적어 준다. (새로 만든다면 Awake()에 적어줘도 된다. -> Awake에 적어주면 base.OnInit()은 빼 주어야 한다.)

public delegate void ThisIsEvent();

protected override void OnInit()
{
    base.OnInit();
    Debug.Log("testChild's OnInit()");
    Debug.Log("setEvent");
    ThisIsEvent del = new ThisIsEvent(WriteLog); // WriteLog를 받음
    ThisIsEvent secondDel = WriteSecondLog; // 이렇게도 대리자를 설정할 수 있음

    del(); // 대리자 실행 (실제는 WriteLog)
    secondDel(); // 두 번째 대리자 실행 (실제는 WriteSecondLog)

}

public void WriteLog()
{
    Debug.Log("EventLog");
}

public void WriteSecondLog()
{
    Debug.Log("Second Event Log");
}

delegate로 대리자(ThisIsEvent())를 정의하고, 대리자 속에 변수에 값 더하듯이 함수 이름을 더해 주면 된다.

 

단, 중요한 점은 더할 함수와 매개변수 형태를 똑같이 정의해 주어야 한다는 점이다.

 

예를 들어, 매개변수가 없다면 없이 하고, int 1개면 int 1개, 이렇게 해 주어야 한다.

 

그리고 대리자를 설정하는 두 가지 방법은 new로 새롭게 생성해 주는 것과, 그냥 더하듯이 해 주는 방법이 있다.

 

위 코드에서는 둘 다 한 개의 함수씩을 넣어 주었다.

 

이렇게 설정 해 주면

 

실행 결과

 

위와 같이 WriteLog와 WriteSecondLog가 실행됨을 볼 수 있다.


한 개의 대리자에 여러 개의 체인을 걸어 주는 경우

 

여러 개의 함수를 한 개의 대리자가 순서대로 수행하게 할 수도 있다.

 

protected override void OnInit()
{
    base.OnInit();
    Debug.Log("testChild's OnInit()");
    Debug.Log("setEvent");
    ThisIsEvent del = new ThisIsEvent(WriteLog); // WriteLog를 받음
    del += WriteSecondLog; // 대리로 실행 할 함수 더하기

    del();

}

del 대리자 한 개에 WriteLog와 WriteSecondLog를 더해 주었다.

 

del을 실행하면

실행 결과

위와 같이 나오게 된다. 

 

더하면 당연히 빼는 것도 가능하다.

 

protected override void OnInit()
{
    base.OnInit();
    Debug.Log("testChild's OnInit()");
    Debug.Log("setEvent");
    ThisIsEvent del = new ThisIsEvent(WriteLog); // WriteLog를 받음
    del += WriteSecondLog; // 대리 목록 추가

    del();

    del -= WriteSecondLog; // 목록 제거

    del(); // 한번 더 실행

}

뒤에 들어간 것부터 순서대로 빼 줘야 한다.

 

실행 결과


Action 대리자

 

Action은 using System; 을 가져와야 사용할 수 있다.

 

그냥 Action으로 만들면 매개변수가 없는 함수만을 대리 리스트에 넣을 수 있다.

 

하지만, 매개변수가 있는 것도 아래처럼 람다로 넣을 수 있음 (익명함수).

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class testChild : test
{
    public delegate void ThisIsEvent();
    public event ThisIsEvent SetEvent;
    public Action actions; // 대리자
    
    // Start is called before the first frame update

    protected override void OnInit()
    {
        base.OnInit();
        Debug.Log("testChild's OnInit()");
        Debug.Log("setEvent");

        actions += WriteLog;
        actions += WriteSecondLog;
        actions += () => WriteItem("InputString");

        actions();

    }

    public void WriteLog()
    {
        Debug.Log("EventLog");
    }

    public void WriteSecondLog()
    {
        Debug.Log("Second Event Log");
    }

    public void WriteItem(string item)
    {
        Debug.Log(item + "is in.");
    }
   
}

실행 결과

이렇게 출력이 되게 된다.


매개변수가 있는 Action 설정

 

Action<T> → T 형식의 매개변수를 넣을 수 있음

 

즉, 매개변수가 있는 대리자를 세팅할 수 있게 됨

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class testChild : test
{
    public Action actions; // 대리자
    public Action<string> stringActions; // string 매개변수를 가지는 대리자
    
    // Start is called before the first frame update

    protected override void OnInit()
    {
        base.OnInit();
        Debug.Log("testChild's OnInit()");
        Debug.Log("setEvent");
        
        stringActions += (str) => WriteItem(str);

        stringActions("input1"); // 대리자 실행 시에 매개변수도 설정 가능
				stringActions("input2");
    }

    public void WriteItem(string item)
    {
        Debug.Log(item + "is in.");
    }

}

차이점이 있다면, 위에서 람다를 사용했을 때는 Action에 추가 할 때, 매개변수의 값을 정하고, 그 값이 고정되는데 반해 매개변수를 넣을 수 있게 세팅 해 주면 들어 가 있는 놈들(함수들)에게 매개변수를 한 번에 설정 해 줄 수 있으며, 실행할 때 마다 다르게 설정할 수 있게 되는 것이다.

 

실행 결과

즉, 더 유연성이 생겼음을 알 수 있다.