개발꿈나무
[C# 교과서] 21. C# 활용(15) - 대리자(delegate), 이벤트(Event) 본문
대리자
대리자(delegate)는 매개변수 목록 및 반환형식이 있는 메서드 참조(포인터)를 나타내는 형식이다.
대리자(위임, 델리게이트)
대리자는 delegate 키워드를 사용하여 만들며, 함수 자체를 데이터 하나로 보고 의미 그대로 다른 메서드를 대신 실행하는 기능이다
- 자동차 개체를 예로 들면, 대리 운전처럼 대리자(대리운전 기사)가 집까지 좌회전(), 우회전(), 직진(), 주차() 등 동작을 대신해서 할 수 있게 하는 개념과 비슷하다.
- 메서드의 매개변수로 대리자 변수(개체)를 넘길 수 있다. 대리자를 사용하여 함수의 매개변수로 함수 자체를 전달할 수 있다.
- 메서드의 매개변수로 또 다른 메서드 호출을 넘기는 기능이다.
- 대리자는 동일한 메서드 시그니처를 갖는 메서드 참조를 담을 수 있는 그릇 역할을 한다.
- 대리자는 람다(lambda)와 개념이 같다고 보아도 된다.
- 대리자를 사용하면 함수를 모아 놓았다 나중에 실행하거나 실행을 취소할 수 있다.
- 대리자는 내부적으로 MulticastDelegate 클래스에서 기능을 상속한다.
- 대리자는 이벤트(event)를 만들어 내는 중간 단계의 키워드로 존재한다.
using System;
class DelegateDemo
{
//[1] 함수 생성 -> 매개 변수도 없고 반환값도 없는 함수
static void Hi() => Console.WriteLine("안녕하세요.");
//[2] 대리자 생성 -> 매개 변수도 없고 반환값도 없는 함수를 대신 실행할 대리자
delegate void SayDelegate();
static void Main()
{
//[A] Hi 함수를 say 이름으로 대신해서 호출
SayDelegate say = Hi;
say();
//[B] Hi 함수를 hi 이름으로 대신해서 호출: 또 다른 모양
var hi = new SayDelegate(Hi);
hi();
}
}
대리자는 [2]처럼 delegate void 함수이름(); 형태로 구현할 수 있다.
대리자는 [A]처럼 이미 만들어 놓은 Hi 함수를 대체할 목적으로 사용할 수 있으며,
Hi 함수를 say 이름으로 대신 호출할 수 있다.
대리자를 사용하여 메서드 대신 호출하기
대리자 변수 = new 대리자(메서드 이름);
대리자 변수 += new 대리자(메서드 이름);
대리자로 함수 대신 호출
델리게이트로 발음하는 대리자는 함수 포인터라고도 하며 다른 함수(메서드)를 대신 호출하는 개념이다.
using System;
class DelegateNote
{
//[1] 대리자 생성: 매개변수도 없고 반환 값이 없는 함수(메서드)를 담을 수 있는 포인터
delegate void SayPointer();
//[2] 샘플 함수 생성
static void Hello() => Console.WriteLine("Hello Delegate");
static void Main()
{
//[A] 대리자의 인스턴스 생성 후 매개변수로 대신 실행할 함수명 전달
SayPointer sayPointer = new SayPointer(Hello);
//[B] 대리자 인스턴스로 함수 대신 호출하는 2가지 방법
sayPointer(); // 대리자 변수에 괄호를 붙여 메서드 호출
sayPointer.Invoke(); // 명시적으로 Invoke() 메서드 호출
}
}
[A] SayPointer라는 delegate 인스턴스를 만들고 실행할 메서드 이름을 지정하는 식으로 대리자 개체 생성 가능
[B] 대리자 인스턴스 개체를 사용하여 메서드를 호출하는 두가지 방법
함수 포인터
대리자 형식은 함수 포인터(function pointer)라고도 하는데 대리자를 생성할 떄 사용되는 delegate 키워드는 이름이 없는 메서드(무명 메서드)를 만들 때도 함께 사용된다.
using System;
class FunctionPointer
{
//[1] 함수 포인터 형식
public delegate void Whats();
static void Main()
{
//[2] 함수 포인터 정의
Whats whats = delegate { Console.WriteLine("함수 포인터 == 대리자"); };
//[3] 함수 포인터 호출
whats();
}
}
대리자를 사용하여 메서드 여러 개를 다중 호출하기
using System;
namespace DelegatePractice
{
public class CarDriver
{
public static void GoForward() => Console.WriteLine("직진");
public static void GoLeft() => Console.WriteLine("좌회전");
public static void GoRight() => Console.WriteLine("우회전");
}
public class Insa
{
public void Bye() => Console.WriteLine("잘가");
}
// [!] 대리자 생성: 의미상으로 대리운전, class와 같은 레벨로 생성해도 됨
public delegate void GoHome();
public class DelegatePractice
{
// [!] 대리자 형식(Type) 선언 : 메서드를 묶을 별칭, 클래스 내부에 생성도 가능
public delegate void Say();
private static void Hello() { Console.WriteLine("Hello"); }
private static void Hi() { Console.WriteLine("Hi"); }
static void Main(string[] args)
{
// [1] 메서드 따로 따로 호출
CarDriver.GoLeft();
CarDriver.GoForward();
CarDriver.GoRight();
// [2] 대리자를 통한 메서드 등록 및 호출
GoHome go = new GoHome(CarDriver.GoLeft);
go += new GoHome(CarDriver.GoForward);
go += new GoHome(CarDriver.GoRight);
go += new GoHome(CarDriver.GoLeft); // 등록
go -= new GoHome(CarDriver.GoLeft); // 취소
go(); // 집에 갑시다... 한번 호출
Console.WriteLine();
// [3] 대리자를 통해서 한번에 2개의 메서드 호출...
Say say; // [a] 대리자 형 변수 선언
say = new Say(Hi); // [b] Hi 메서드 지정
say += new Say(Hello); // [c] Hello 메서드 지정
say(); // [d] 대리자로 두 개의 메서드 호출
// [4] 대리자를 통해서 호출
Insa insa = new Insa();
Say say2 = new Say(insa.Bye);
say2 += new Say(insa.Bye);
say2();
}
}
}
무명 메소드
// 무명 메서드(Anonymous Method): 대리자를 축약해서 표현하는 방법
using System;
namespace AnonymousMethod
{
public class Print
{
public static void Show(string msg) => Console.WriteLine(msg);
}
public class AnonymousMethod
{
//[!] 대리자 선언
public delegate void PrintDelegate(string msg);
public delegate void SumDelegate(int a, int b);
static void Main()
{
//[1] 메서드 직접 호출
Print.Show("안녕하세요.");
//[2] 대리자에 메서드 등록 후 호출
PrintDelegate pd = new PrintDelegate(Print.Show);
pd("반갑습니다.");
//[3] 무명(익명) 메서드로 호출: delegate 키워드로 무명 메서드 생성
PrintDelegate am = delegate (string msg)
{
Console.WriteLine(msg);
};
am("또 만나요.");
//[4] 무명메서드 생성 및 호출
SumDelegate sd = delegate (int a, int b) { Console.WriteLine(a + b); };
sd(3, 5); // 8
}
}
}
대리자 개체에 람다 식 담기
using System;
class LambdaExpression
{
//[1] 대리자 선언
delegate void Lambda();
static void Main()
{
//[2] 대리자 개체에 람다식 정의: goes to 연산자
Lambda hi = () => Console.WriteLine("안녕하세요.");
//[3] 대리자 개체 호출
hi();
}
}
using System;
class LambdaExpressionArgs
{
//[1] 매개 변수도 있고 반환값도 있는 대리자 선언
delegate int Lambda(int i);
static void Main()
{
//[2] 람다 식으로 대리자 개체 생성
Lambda square = x => x * x;
Console.WriteLine(square(3)); // 9
Console.WriteLine(square(4)); // 16
}
}
메서드의 매개변수에 대리자 형식 사용하기
using System;
class DelegateParameter
{
delegate void Runner();
static void Main()
{
RunnerCall(new Runner(Go)); // "직진"
RunnerCall(new Runner(Back)); // "후진"
}
static void RunnerCall(Runner runner) => runner(); // 넘어온 메서드(함수) 실행
static void Go() => Console.WriteLine("직진");
static void Back() => Console.WriteLine("후진");
}
Action, Func, Predicate 대리자
닷넷 API에 내장된 유용한 제네릭 대리자에넌 Action, Func, Predicate 가 있다.
- Action 대리자: 반환값이 없는 메서드를 대신 호출한다.
- Func 대리자: 매개변수와 반환값이 있는 메서드를 대신 호출한다.
- Predicate 대리자: T 매개변수에 대한 bool 값을 반환하는 메서드를 대신 호출한다.
Action<T> 대리자 사용하기
Action 제네릭 대리자를 사용하면 Console.WriteLine 같은 메서드를 대신 호출할 수 있다.
Action<string> printf = Console.WriteLine;
printf("메서드 대신 호출");
Func<T> 대리자 사용하기
Func<매개변수 형식, 반환값 형식>으로 특정 메서드 또는 익명 메서드를 대신 호출할 수 있다.
Func 제네릭 대리자는 람다 식을 포함한 무명 메서드 또는 일반 메서드를 대신 호출할 수 있다.
using System;
class FuncDemo
{
static void Main()
{
// [1] int를 입력 받아 0이면 true을 반환
Func<int, bool> zero = number => number == 0;
Console.WriteLine(zero(1234 - 1234)); // True
// [2] int를 입력 받아 1을 더한 값을 반환
Func<int, int> one = n => n + 1;
Console.WriteLine(one(1)); // 2
// [3] int 2개를 입력 받아 더한 값을 반환
Func<int, int, int> two = (x, y) => x + y;
Console.WriteLine(two(3, 5)); // 8
}
}
Fucn 대리자로 메서드 또는 람다 식 대신 호출하기
using System;
class FuncDelegate
{
static void Main()
{
// [1] Add 함수 직접 호출
Console.WriteLine(Add(3, 5));
// [2] Func 대리자로 Add 함수 대신 호출: 반환값이 있는 메서드를 대신 호출
Func<int, int, string> AddDelegate = Add; // Add 메서드를 대신 호출
Console.WriteLine(AddDelegate(3, 5));
// [3] 람다식(Lambda): 메서드 -> 무명메서드 -> 람다식으로 줄여서 표현
Func<int, int, string> AddLambda = (a, b) => (a + b).ToString();
Console.WriteLine(AddLambda(3, 5));
}
static string Add(int a, int b) => (a + b).ToString();
}
Func 대리자를 사용하면 동일한 매개변수와 반환값이 있는 메서드를 대신해서 호출할 수 있다.
Predicate 대리자 사용하기
Predicate<T> 대리자는 T를 매개변수로 받아 어떤 로직을 수행한 후 그 결과를 bool 형식으로 반환하는 메서드를 대신 호출한다.
using System;
public class PredicateDemo
{
static void Main()
{
Predicate<string> isNullOrEmpty = String.IsNullOrEmpty;
Console.WriteLine(isNullOrEmpty("Not Null"));
Predicate<Type> isPrimitive = t => t.IsPrimitive;
Console.WriteLine(isPrimitive(typeof(int)));
}
}
이벤트
이벤트는 특정 상황이 발생할 때 개체 또는 클래스에서 알림을 제공할 수 있도록 하는 멤버로, 버튼 클리고가 마우스 오버 같은 이벤트 기반 프로그래밍에 사용하는 개념이다.
이벤트
- 이벤트는 개체의 메서드 실행 결과(사고)를 나타낸다.
- 자동차 개체를 예로 들면, 과속이라는 동작(메서드)의 수행 결과는 교통사고라는 이벤트(사고)가 발생한다는 의미로 해석할 수 있다.
- 웹 응용 프로그래밍 및 데스크톱 응용 프로그램은 이벤트 기반 프로그래밍이라고 할 정도로 많은 이벤트를 사용한다.
- 마우스 클릭 이벤트, 마우스 오버 이벤트, 마우스 아웃 이벤트 등을 표현할 때는 이벤트 기능으로 정의한다.
프로그래밍에서 이벤트(event)와 이벤트 처리기(event handler)는 다음과 같이 표현된다.
- 이벤트: 클릭과 마우스 오버 같은 동작(트리거)
- 이벤트 처리기: 특정 이벤트를 담당하려고 만든 메서드
이벤트와 대리자를 사용하여 메서드 등록 및 호출하기
대리자는 이벤트를 위한 중간 단계고, 이벤트는 메서드 여러 개, 특히 이벤트 처리 전용 메서드라는 이벤트 처리기(핸들러) 메서드를 등록한 후 실행시키는 역할을 한다.
using System;
public class ButtonClass
{
//[1] 이벤트 생성을 위한 대리자 하나 생성
public delegate void EventHandler(); // 여러 개 메서드 등록 후 호출 가능
//[2] 이벤트 선언: Click 이벤트
public event EventHandler Click;
//[3] 이벤트 발생 메서드: OnClick 이벤트 처리기(핸들러) 생성
public void OnClick()
{
if (Click != null) // 이벤트에 등록된 값이 있는지 확인(생략 가능)
{
Click(); // 대리자 형식의 이벤트 수행
}
}
}
class EventDemo
{
static void Main()
{
//[A] Button 클래스의 인스턴스 생성
ButtonClass btn = new ButtonClass();
//[B] btn 개체의 Click 이벤트에 실행할 메서드들 등록
btn.Click += Hi1; // btn.Click += new ButtonClass.EventHandler(Hi1);
btn.Click += Hi2; // btn.Click += new ButtonClass.EventHandler(Hi2);
//[C] 이벤트 처리기(발생 메서드)를 통한 이벤트 발생: 다중 메서드 호출
btn.OnClick();
}
static void Hi1() => Console.WriteLine("C#");
static void Hi2() => Console.WriteLine(".NET");
}
<Reference>
'C# 기초' 카테고리의 다른 글
[C# 교과서] 23. C# 활용(17) - 메서드 오버라이드 (0) | 2022.01.20 |
---|---|
[C# 교과서] 22. C# 활용(16) - 상속으로 클래스 확장하기 (0) | 2022.01.20 |
[C# 교과서] 20. C# 활용(14) - 인덱서와 반복기 (0) | 2022.01.20 |
[C# 교과서] 19. C# 활용(13) - 속성 사용하기 (0) | 2022.01.20 |
[C# 교과서] 18. C# 활용(12) - 메서드와 매개변수 (0) | 2022.01.20 |