2023년 1월 2일 월요일

C# 이벤트와 델리게이트의 차이

 <C# 이벤트 & 델리게이트 차이>


[이벤트]


선언:

접근자 event 델리게이트 이벤트이름;

예) public event EventArgs MyEventName;


용도: 

클래스 내부에서 어떤일이 일어났음을 알리기 위함이나 추가작업의 용도로 사용되며
실행주체는 이벤트가 있는 클래스다.
선언에서 델리게이트로 일반적으로 EventHandler를 사용한다. 
(물론 다른 델리게이트가 와도 된다.)

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

델리게이트인 Event핸들러의 내용을 보면 위처럼 되어있는데 전달될수 있는
TEventArgs는 기본타입이랑 EventArgs를 구현한 클래스만된다. 인자로 한개만
전달한다.. 하지만 직접구현한 델리게이트를 사용할수도 있기는한다.

그리고 이벤트는 namespace 바로 밑에서 선언될수 없고 클래스나 인터페이스
내부에서만 선언될수 있다. 클래스 내부에서만 호출(Invoke) 될수 있다. 
델리게이트와 달리 보안이 좀더 들어갔다고 보면된다.
단적인 예로 클래스 event 변수에대고 = null 을 할수 없다.  함수바인딩용으로 +=
이나-= 연산자만 사용된다.  델리게이트와 달리 캡슐화가 좀더 됬다고 볼수 있다.


사용:

이벤트가 있는 클래스에서 해당이벤트 변수를 += 연산자로 하여 호출자 클래스에서
선언한 함수로 바인딩한다.
이벤트가 있는 클래스는 해당함수가 호출될때 invoke 메서드를 통하여 이벤트를
수신하고있는 수신자에게 알린다.

바인딩 유무와 관계가 있으므로 invoke메서드 호출하기 전에 ? 연산자로 null체크를
해야한다. 전달인자에는 EventArgs가 들어가는데 추가적으로 전달하고 싶은값이
있으면 EventArgs클래스를 상속받는 클래스를 만들어서 거기에다가 추가 변수를 달고
내용을 기재후 전달하면 된다.
메모리누수가 있을수 있으니 호출자 클래스가 종료될때 -= 연산자를 통해서 내부로
바인딩된 함수연결을 해제한다. 굳이 따지자면 델리게이트의 하위개념이다.
캡슐화가된 알림용도및 호출자의 추가작업 용도로만 쓰기위해 별도로 하나만
빼놓은것이다. 그래서 일부사람들은 델리게이트를 캡슐화 했다는 의미에서
event 키워드를 event 한정자 라고 부르기도 한다.


[델리게이트]


원형:

접근자 delete 반환형 델리게이트이름(매개변수);

예)  public delegate void ExampleDelegate(int total);


용도: 

함수내부에서 어떤 함수의 내용이 추가 혹은 완전 대체하기 위해 사용된다. 
(그 추가작업이 이벤트처럼 알림이 될수 있다. 그리고 책을 보면 이벤트와 델리게이트
항상 붙어서 나오니까 비교하면서 생각하다보면 혼란의 도가니로 빠진다. 
event 있는데 애는 또 왜있는거야??)

이름이 델리게이트 즉 대리자 사실 이것이 핵심단어이다. 결론적으로 주 함수를
대신해서 어떤작업을 할때 주로 사용된다.
작업은 외부에 구성되어 있지만 이벤트와 마찬가지로 실제호출하는 주체는
델리게이트를 호출하는 클래스이기 때문에 만약 다른쓰레드에서 호출된다면 동기화
처리과정을 거쳐야한다.
작업이 외부에서 구성되어 있다고 로직이 외부에만 있다는건 아니다.
내부에서 있을수도 있고 코드 짜는 사람 마음이다.
같은애기로다가 같은 클래스에서 선언도하고 호출 할수도 있다. 하지만 이렇게
하는건 대리자의 의미가 없다.
그럴꺼면 그냥 그함수안에다가 로직을 그냥 작성하는것이 낫다.  분리가 될수
있다가 아니라 분리해서 어쩌자는거야?? 이게 중요한거다.. 그래서 단순 함수포인터처럼
생각하고 달라들다가는 큰코 다칠수 있다. 이게 느슨한결합과 관심사의 분리를
가능하게 해주는 기술중에 하나이다.. 이런식으로 접근하면 좋다.


내용: 

함수의 원형만 가지고 있는형태라서 이 원형만 지켜지면 이름에 상관없이 어떤함수라도
호출할수 있다. 델리게이트를 가지고 있는 함수는 이 델리게이트 가지고 뭘하는지 별로
관심없다. (느슨한결합, 관심사분리 이런거 하라고 있는거다)
형태만 맞으면 장땡이라서 이벤트와 비교를 해서 굳이 따지자면 변수가 아니라 그냥
선언 타입 이라는것?? 
그리고 델리게이트가 변수로 선언된다면 이벤트처럼 += 연산자를 통해서 함수연결을
추가할수 있다.
c언어를 아는 사람은 이벤트는 알림기능이 있는 오브젝트이고 델리게이트는 그냥
함수포인터다 라고 생각하면 되긴한다.
사용은 이름이 있는 일반함수도 사용되지만 익명함수로도 사용이된다.


편의성: 

delegate선언 그자체가 형식이라서 변수를 생성하기 위해서는 delegate 이름을 만들고
변수명을 한번 더 선언해야한다. delegate 선언은 그냥 인터페이스처럼 함수원형이 있는
시그니처 라고 보면된다. 그런데 프로그래밍을 하다보면 함수가 1~2개만 있는것도
아니고 관심사분리를 위해 대리자를 사용할때 타입도 다르고 리턴도 다른 delegate를
그때마다 계속 선언해야한다.
위행동을 그때그때 계속 선언하기 귀찮으니까 이를 대체하기 위한 용도로 제네릭 기술을
이용해서 리턴이 없는 Action과 리턴이 있는 Func라는  2개의 특수한 익명함수용
클래스를 미리 만들어놨다.

Action<in T1, in T2>  Func<in T1, in T2, out Tr> 이렇게..

헷갈릴수 있는것은 선언이 델리게이트 랩핑된 Action<T1, T2> action 이런식이고
사용이 이함수의 익명함수나 이름이있는 함수가 온다.  이를 헷갈리면 안된다.
그냥 delegate 어쩌구 저쩌구의 선언된 표현을 Action과 Func로 축약했다. 이정도로만
이해하면 된다.

이 Action 과 Func는 무조건적으로 대체되어 쓸수 없는 경우도 있는데 그건 바로 선언부
파라미터를 보면 in 혹은 out 키워드로 한정자가 붙었다 그래서 인자를 전달하면서
그 인자에다가 어떤 결과를 삽입하여 사용되는 out 파라미터의 경우에는 이 2개를
사용하지 못한다.  뭐 그럴일은 상대적으로 적긴하겠지만..

이름이 있는 함수를 사용해서 구현되는것도 가능하지만 이름이 없이 좀더 간편하게
람다식으로 () => { }  이렇게 되었있는 구현으로도 자주 활용된다.
linq의 확장메서드 Sum을 잘 생각해보면 Enumerate가능한 컬랙션이 오고 그결과들의
합을 반환 받는데 Sum 함수는 전달하는 인자가 무엇이는 상관하지 않는다는걸
알수있다.  마찬가지로 Sort도 정렬방식에 해당하는 부분의 구현을 위임함으로써
관심사의 분리가 되어있다. 그래서 재활용성이 뛰어나다.


어쨌든 둘다 따지고 들어가다 보면 함수포인터이다.  델리게이트가 근본적인
함수포인터에 더 가깝고이벤트가 델리게이트의 부속품 정도이다. 그렇지만
별도의 타이틀이 있는만큼 용도적인 측면에 있어서 비슷하게 생각하기 쉽다.

하지만 이를 MS에서 구분을 해놨으니까 C#에서 사용하는 사람들은 용도에따라서
구분하여 사용하면된다. 델리게이트가 먼저나오고 이벤트가 나중에 나오면서
델리게이트를 좀더 사용하기 쉽고 안전하게 캡슐화하여 만든경향이 있는데
MS에서 이 2개를 통합하지않고 가만히 두는걸 보면 2개를 구분해서 사용하기를
원하는것 같다.

C#배우는사람 입장에서 이 2가지를 이해하려면 button 이벤트 처럼 이벤트를
먼저 익히고 그 다음 델리게이트의 내용을 관심사의 분리관점에서 익히고 다시
이벤트로 돌아와서 캡슐화측면에서 이벤트를 바라봐야지만 이 2개를 대충 이해할수 있다.

그리고 내가 배울때 의아했던 부분인데 느슨한결합과 관심사의 분리가 된다고 해서
코드가 될때가 있고 안될때가 있다는 말이 아니고 잘돌아가는데 거기에 레고블럭처럼
바꿔서 재활용할수 있는 경우의 수가 늘어난다고 처음에는 이해하면된다.


댓글 없음:

댓글 쓰기