[C#] Delegate, Action, Func 정리

Gee·2025년 4월 4일

델리게이트

  • 델리게이트는 int, string 같은 타입
  • 여러가지 함수들을 가지고 있을 수 있다. (리스트나 딕셔너리도)
  • Private delegate void BasicDelegate();
    • 반환형이 없는 void 반환형을 들고 있고 매개변수가 없는 함수들만 받을 수 있는 델리게이트 라는 뜻
    • 현재는 BasicDelegate();라는 타입을 만든 것
Private delegate void BasicDelegate();
Private BasicDelegate startProcess;

private void Awake()
{
	startProcess = SayHI;
}

Void Start()
{
	startProcess();
	startProcess?.Invoke();	// 또는 이렇게. ? <- startProcess가 없으면 말어~ 있으면 Invoke
}

public void SayHI()
{
    Debug.Log("안녕~");
}
  • 이렇게 해주고 플레이 하면 콘솔에 안녕~ 이 뜨게된다.
  • SayHI 함수를 델리게이트에 저장해 사용한 것
  • Private BasicDelegate startProcess;는 다시 선언해 객체를 만든건데, private int a; 와 같다고 생각하면 쉽다.
  • BasicDelegate = SayHi;가 안되는 이유는.. 똑같이 int = a라고 하는 것과 같음. 이상함
  • delegate는 타입이니까 변수명인 startProcess를 사용해 startProcess = SayHi 라고 쓴 것
Private delegate void BasicDelegate();
Private BasicDelegate startProcess;

    private void Awake()
    {
        startProcess = SayHI;
        startProcess = Digimon;	// 추가
    }

    Void Start()
    {
        startProcess();
        startProcess?.Invoke();	
    }

    public void SayHI()
    {
        Debug.Log("안녕~");
    }

    public void Digimon()	// 추가
    {
        Debug.Log("디지몬~");
    }
  • 이러고 실행하면 디지몬~ 만 뜬다.
  • 덮어씌워진 것
string name = "홍";
name = "길동";
Debug.Log(name);

> 출력: 길동
  • 이렇게 해버린 것과 같다.
string name = "홍";
name += "길동";
Debug.Log(name);

> 출력: 홍길동
  • 이렇게 해야겠지. 델리게이트도 마찬가지다.
  • startProcess += Digimon; 이렇게 +를 넣어줘야 함
    private string ShowInfo(int num)
    {
        return ($"내 출석번호는 {num}번이야");
    }
  • 이제 이렇게 void 대신에 반환형과 매개변수가 있는 함수를 넣을 수 있는 델리게이트를 선언해 보자.
private delegate string StringDelegate(int a);
  • 이렇게 하면 된다.
  • string을 반환 받을 수 있는, int a를 매개변수로 받는 델리게이트 타입이라는 뜻
private delegate string StringDelegate(int a);
private event StringDelegate stringProcess;

	private void Awake()
    {
        stringProcess = ShowInfo;
        
        Debug.Log(stringProcess.Invoke(1));
        Debug.Log(stringProcess(2));
	}
    
    private string ShowInfo(int num)
    {
        return ($"내 출석번호는 {num}번이야");
    }
  • 이러면 내 출석번호는 1번이야, 내 출석번호는 2번이야가 출력된다.
  • 근데 만약 반환형이 있는 델리게이트에 함수가 여러 개 들어가 있으면 어떻게 될까?
  • 그때는 마지막으로 추가한 메서드의 반환값이 return된다.
private delegate string StringDelegate(int a);
private event StringDelegate stringProcess;

	private void Awake()
    {
        stringProcess = ShowInfo;
        stringProcess += ShowSomething;
        
        Debug.Log(stringProcess.Invoke(1));
	}
    
    private string ShowInfo(int num)
    {
        return ($"내 출석번호는 {num}번이야");
    }

    private string ShowSomething(int num)
    {
        return ($"맻기야! 네! {num}기 입니다!");
    }
  • 추가한 함수에는 += 처리를 해줘야 함
  • 이러고 실행해 보면 맻기야! 네! 1기 입니다!가 출력된다.
  • 마지막으로 추가된 함수의 반환값이 출력되게 되는 것.
  • 근데 너무 귀찮지 않나? 싶어서 이걸 좀 더 간단하게 바꿀 수 있는 새 기능이 두 가지 생김
  • 그게 바로 Action과 Func.

Action

Action / Func 차이?

  • Action은 반환값이 없고(무조건 void) Func은 반환값이 있음(Something)
  • Action은 void 함수를 받을 수 있는 델리게이트 타입, Func은 반환값이 있는 함수를 받을 수 있는 델리게이트 타입
	private Action startAction;

	private void Awake()
    {
        startAction = SayHI;
        startAction.Invoke();
	}
    
    public void SayHI()
    {
        Debug.Log("안녕~");
    }
  • Action을 사용한 델리게이트 사용
  • 이렇게 하면 안녕~ 이 실행됨
	private Action startAction;

	private void Awake()
    {
        startAction = SayHI;
        startAction = Digimon;	// 추가
        startAction.Invoke();
	}
    
    public void SayHI()
    {
        Debug.Log("안녕~");
    }
    
    public void Digimon()	// 추가
    {
        Debug.Log("디지몬~");
    }
  • 근데 이런 식으로 디지몬을 추가하면 델리게이트와 같이 디지몬~ 만 나오게 된다.
  • 역시 두 개가 필요하면 startAction += Digimon; 이렇게 += 처리를 해줘야 함. (뺄거면 -=를 해주면 되겠지)
private Action<> startAction;
  • Action의 <>에는 뭐가 들어갈까? Action은 반환형이 void라고 했으니 int 같은 건 아님
  • 매개변수가 들어간다.
	private Action<string> stringAction;

	private void Awake()
    {
        startAction = SayHI;
        startAction = Digimon;
        startAction.Invoke();
        
        stringAction += Pokemon;	// 추가
        stringAction("이상해씨");	// 추가
	}
    
    public void Pokemon(string p)	// 추가
    {
        Debug.Log($"가라 {p} 너로 정했다");
    }
  • 이렇게 string p를 매개변수로 받는 함수라면 <>안에 string을 넣어주면 되는 것
  • 이 <>는 매개변수를 16개까지 받을 수 있다.
  • 이러면 디지몬~, 가라 이상해씨 너로 정했다가 출력된다.

Func

  • Func은 반환값이 있는 함수를 받을 수 있는 델리게이트 타입
private Func startFunc;
  • 첫 Action처럼 간략하게 적으려 하면 여기서는 에러가 뜨게 됨
  • 왜? 반환값이 없기 때문에. Func은 무조건 void가 아닌 반환형을 미리 지정해 줘야 함
	private Func<string> startFunc;
    
    private void Awake()
    {
        startFunc = ShowTV;
        Debug.Log(startFunc.Invoke());
	}

    private string ShowTV()
    {
        return "난 TV를 켰어~";
    }
  • 이렇게 private string ShowTV()를 받으려면 <>에 string을 추가해 주면 된다.
  • 실행해 보면 난 TV를 켰어~ 가 출력된다.
	private Func<string> startFunc;
    
    private void Awake()
    {
        startFunc = ShowTV;
        Debug.Log(startFunc.Invoke());
	}

    private string ShowTV()
    {
        return "난 TV를 켰어~";
    }

    private string ShowProgram(int channelNum)	// 추가
    {
        return $"나는 지금 {channelNum}번을 보고 있어";
    }
  • 근데 이렇게 string 반환값에 int 매개변수가 있는 걸 받으려면 어떻게 해야 할까?
  • private Func<string> startFunc; 여기서 추가로 int를 받아야 될 것 같다.
    private Func<string, int> stringFunc;	// 추가
    
    private void Awake()
    {
        stringFunc = ShowProgram;	// 추가
	}
    
    private string ShowProgram(int channelNum)
    {
        return $"나는 지금 {channelNum}번을 보고 있어";
    }
  • 근데 이렇게 하면 stringFunc = ShowProgram; 부분에 에러가 뜬다.
  • 왜냐면 순서가 잘못됐음. 매개변수(int) 먼저 지정을 해주고 마지막에 반환형을 입력해야 한다.
    private Func<int, string> stringFunc;	// 수정
    
    private void Awake()
    {
        stringFunc = ShowProgram;	
        Debug.Log(stringFunc.Invoke(7));	// 추가
	}

    private string ShowProgram(int channelNum)
    {
        return $"나는 지금 {channelNum}번을 보고 있어";
    }
  • 이렇게 수정해 주고 실행해 보면 나는 지금 7번을 보고 있어가 출력된다.

event

  • 델리게이트, Action, Func은 모두 이벤트를 사용하는 것이라고 말할 수 있다. (이벤트를 받을 수 있는 것)
private delegate string StringDelegate(int a);
private event StringDelegate stringProcess;
  • 그래서 이렇게 event 키워드를 붙일 수 있다.
private event Action startAction;
private Action<string> stringAction;

private event Func<string> startFunc;
private Func<int, string> stringFunc;
  • 이렇게 Action과 Func에도 event를 붙일 수 있다.
  • event는 델리게이트의 특별한 형태로, 외부에서 직접 호출할 수 없고 오직 +=와 -= 연산을 통해서만 접근할 수 있도록 제한하는 기능을 제공함
  • 이를 통해 다른 클래스에서 이벤트를 임의로 실행(Invoke())하지 못하도록 보호할 수 있다.

event를 사용하는 이유

  • 델리게이트는 함수를 담을 수 있는 변수라고 볼 수 있는데, 델리게이트 변수 자체를 외부에서 변경할 수 있다는 단점이 있다.
public class EventTest
{
    public delegate void MyDelegate();
    public MyDelegate myEvent;

    public void Start()
    {
        myEvent = SayHi;  // 외부에서 myEvent를 덮어씌울 수 있음
        myEvent?.Invoke();
    }

    public void SayHi()
    {
        Debug.Log("안녕~");
    }
}
  • 위 코드에서 myEvent는 public 필드이므로 외부 클래스에서 덮어쓰기(=)가 가능
  • 즉 +=를 사용해야 하는데, 실수로 =를 사용하면 기존의 이벤트 핸들러가 모두 사라지게 됨
  • 이 문제를 막기 위해 event를 사용하면 = 연산으로 덮어쓰는 것을 막을 수 있음.
public class EventTest
{
    public delegate void MyDelegate();
    public event MyDelegate myEvent;  // event 키워드 추가

    public void Start()
    {
        myEvent += SayHi;  // 외부에서는 +=, -= 만 가능
        myEvent?.Invoke();  // 실행 가능
    }

    public void SayHi()
    {
        Debug.Log("안녕~");
    }
}
  • 그래서 이렇게 델리게이트 사용 시 event 키워드를 추가해 주면 외부에서 직접 호출할 수 없게 된다. (+=와 -=만 사용 가능)

UnityEvent

    public UnityEvent uEv;
    private UnityAction uAct;
  • 이건 유니티에서만 쓸 수 있는 Event
  • 이 두 개는 Action과 같이 반환값이 없는 함수들만 가질 수 있고 매개변수는 4개밖에 못 받음
  • 그럼 왜 씀? 이라고 묻는다면 각각 특징과 장점이 있다.
  • UnityEvent는 직렬화가 가능하다. 이 말은 인스펙터 창에서 확인이 가능하다는 뜻
  • 유니티 인스펙터 창으로 가보면 uEv란이 생긴 걸 볼 수 있다. 버튼 설정 창과 비슷하게 사용할 수 있다.
  • 그럼 UnityAction도 직렬화가 가능하나요? 아니요. 또한 그냥 Action보다도 성능이 안좋음
  • 그럼 진짜 왜 씀? 정의하기로는 Unity에 연관되어 있어서 유니티 특화 기능을 사용할 때 좋다고 함. 근데 흠..
    public UnityEvent uEv;
    
    public void Start()
    {
        uEv.AddListner(SayHI);
        uEv.Invoke();
    }
    
    public void SayHi()
    {
        Debug.Log("안녕~");
    }
  • UnityEvent는 이렇게 쓸 수 있다.
  • 이건 사용할 때 Invoke();만 쓸 수 있음
    private UnityAction uAct;
    
    public void Start()
    {
        uAct += SayHI;
        uAct.Invoke();
    }
    
    public void SayHi()
    {
        Debug.Log("안녕~");
    }
  • uAct은 uAct = SayHI;, uAct += SayHI; / uAct();, uAct.Invoke(); 둘 다 가능
    public UnityEvent uEv;
    private event UnityAction uAct;
  • 그리고 event는 UnityAction만 가능. UnityEvent는 추가하면 에러가 뜬다.

정리

  • 그럼 이것들을 다 어디다 쓰냐? 주로 콜백 함수에 쓴다.
  • 콜백 함수는 예를 들어 코루틴 같은 것들을 쓸 때.
    IEnumerator SomeProcess(Action<int> afterProcess)
    {
        Debug.Log("서버에 뭔가 보냄");
        yield return new WaitForSeconds(2);
        afterProcess.Invoke(1);
    }
  • 코루틴은 반환값이 없어서 반환을 할 수 없음
  • 근데 해당 프로세스가 끝나고 무조건 뭔가가 진행됐음 좋겠다면 이렇게 해주면 됨
profile
...

0개의 댓글