[This is C#] Chapter 13. 대리자와 이벤트
Table of Contents
이것이 C#이다 책을 읽고 공부한 노트입니다.
대리자 #
- 대리자(Delegate)
- 메소드에 대한 참조이다.
// 델리게이트 형식(Type)
delegate int MyDelegate(int a, int b);
class Calc
{
public int Add(int x, int y)
{
return x + y;
}
public int Minus(int x, int y)
{
return x - y;
}
}
class MainApp
{
static void Main(string[] args)
{
Calc calc = new Calc();
// 델리게이트 인스턴스 생성 및 등록
MyDelegate callback = new MyDelegate(calc.Add);
// 델리게이트 호출
Console.WriteLine($"{callback(3, 4)}"); // 7
callback = new MyDelegate(calc.Minus);
Console.WriteLine($"{callback(5, 4)}"); // 1
}
}
대리자는 왜, 언제 사용할까? #
delegate int Compare(int a, int b);
class MainApp
{
static int AscendCompare(int a, int b)
{
if (a > b) return 1;
else if (a == b) return 0;
else return -1;
}
static int DescendCompare(int a, int b)
{
if (a < b) return 1;
else if (a == b) return 0;
else return -1;
}
// 델리게이트를 매개변수로 사용함
static void BubbleSort(int[] data, Compare comparer)
{
int temp = 0;
for (int i = 0; i < data.Length - 1; i++)
{
for (int j = 0; j < data.Length - (i + 1); j++)
{
if (comparer(data[j], data[j + 1]) > 0)
{
temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
}
static void Main(string[] args)
{
int[] arr = {8, 7, 3, 5, 1};
BubbleSort(arr, new Compare(AscendCompare));
for (int i = 0; i < arr.Length; i++)
Console.Write($"{arr[i]} "); // 1 3 5 7 8
}
}
일반화 대리자 #
delegate int Compare<T>(T a, T b);
class MainApp
{
// System.Int32와 같은 모든 수치 형식과 System.String은 모두 IComparable을 상속해서 CompareTo()를 구현하고 있다.
// CompareTo()는 매개변수가 자신보다 크면 -1, 같으면 0, 작으면 1을 반환한다.
static int AscendCompare<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b);
}
static int DescendCompare<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) * -1;
}
// 델리게이트를 매개변수로 사용함
static void BubbleSort<T>(T[] data, Compare<T> comparer)
{
T temp;
for (int i = 0; i < data.Length - 1; i++)
{
for (int j = 0; j < data.Length - (i + 1); j++)
{
if (comparer(data[j], data[j + 1]) > 0)
{
temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
}
static void Main(string[] args)
{
int[] arr = {8, 7, 3, 5, 1};
BubbleSort<int>(arr, new Compare<int>(AscendCompare));
for (int i = 0; i < arr.Length; i++)
Console.Write($"{arr[i]} "); // 1 3 5 7 8
}
}
대리자 체인 #
- 대리자 하나가 여러 개의 메소드를 동시에 참조할 수 있다.
delegate void Notify(string mesg);
class MainApp
{
static void Call119(string location)
{
Console.WriteLine("Call119");
}
static void ShotOut(string location)
{
Console.WriteLine("ShotOut");
}
static void Escape(string location)
{
Console.WriteLine("Escape");
}
static void Main(string[] args)
{
// 델리게이트 체인 (추가 방법1)
Notify notify1 = new Notify(Call119);
notify1 += ShotOut;
notify1 += Escape;
// (추가 방법2)
Notify notify2 = new Notify(Call119) + new Notify(ShotOut) + new Notify(Escape);
// (추가 방법3)
Notify notify3 = (Notify) Delegate.Combine(new Notify(Call119), new Notify(ShotOut), new Notify(Escape));
// (삭제 방법1)
notify1 -= Call119; // ShotOut Escape
// (삭제 방법2) notify3에서 notify2에 있는 것을 뺀다.
notify3 = (Notify) Delegate.Remove(notify3, notify1); // Call119
}
}
익명 메소드 #
- 익명 메소드(Anonymous Method)
- 이름이 없는 메소드이다.
- 메소드가 델리게이트에 의해 참조 된 후로 사용할 일이 다시 없다고 한다면 익명 메소드로 선언하면 된다.
delegate int Calculate(int a, int b);
class MainApp
{
static void Main(string[] args)
{
// 익명 메소드
Calculate calc = delegate(int a, int b)
{
return a + b;
};
calc(1, 3); // 4
}
}
이벤트 #
-
이벤트
- 델리게이트에
event
키워드를 붙여서 사용한다.
- 델리게이트에
-
이벤트와 델리게이트의 차이점
- 이벤트는
public
한정자로 선언돼 있어도 자신이 선언된 클래스 외부에서는 호출이 불가능하다.
- 이벤트는
// 델리게이트 형식
delegate void Notify(string mesg);
class MyClass
{
// 델리게이트 인스턴스 생성 + event 한정자
public event Notify OnSomethingHappened;
public void DoSometing(int number)
{
// 이벤트 호출
if (number % 2 == 0)
OnSomethingHappened(String.Format("{0} : 짝", number));
}
}
class MainApp
{
static public void PrintMesg(string mesg)
{
Console.WriteLine(mesg);
}
static void Main(string[] args)
{
MyClass myClass = new MyClass();
// 이벤트 등록
myClass.OnSomethingHappened += new Notify(PrintMesg);
for (int i = 1; i < 30; i++)
{
myClass.DoSometing(i);
}
// event 한정자를 붙이면 클래스 밖에서 호출이 안 된다.
myClass.OnSomethingHappened("클래스 밖에서 호출해보기"); // 컴파일 에러!
}
}
미리 정의된 대리자들 #
- 델리게이트는 형식을 만들고, 인스턴스를 또 생성하여서 사용해야한다.
- 모든 타입의 형태로 미리 정의된 델리게이트를 간단하게 쓸 수 있는 기능이 바로
Action
과Func
이다. 이것들은 별도로 미리 정의하지 않아도 한 줄로 표현 가능하다.
// delegate의 경우 미리 정의해야 한다.
delegate void MyDelegate(string mesg);
public class MyClass
{
static void Main(string[] args)
{
// 그리고 인스턴스를 만들어서 사용한다.
// C# 2.0에서는 new 없이 간단하게 인스턴스를 만들 수 있다.
MyDelegate myDelegate = ShowMessage;
myDelegate("Hello");
}
static void ShowMessage(string mesg)
{
Console.WriteLine(mesg);
}
}
public class MyClass
{
static void Main(string[] args)
{
// Action의 경우 바로 쓸 수 있다.
Action<string> myAction = ShowMessage;
myAction("Hello");
}
static void ShowMessage(string mesg)
{
Console.WriteLine(mesg);
}
}
Action 대리자 #
- 반환값이 없는 대리자이다.
- 매개변수를 16개까지 넣을 수 있다.
Action
,Action<T>
,Action<T1, T2>
…
Func 대리자 #
- 반환값이 1개 있는 대리자이다.
- 매개변수를 16개까지 넣을 수 있다.
Func<TResult>
,Func<T, TResult>
,Func<T1, T2, TResult>
…
public class MyClass
{
static void Main(string[] args)
{
Func<int, int, int> myAction = Add;
Console.WriteLine(myAction(1, 2));
}
static int Add(int a, int b)
{
return a + b;
}
}
EventHandler 대리자 #
- 형식이 지정된 대리자이다.
- 반환값은 없다.
- 첫 번째 매개변수는
object
이며 이벤트를 발생시키는 인스턴스를 참조한다. - 두 번째 매개변수는
EventArgs
에서 파생된 것이며 이벤트 데이터를 보유한다.
EventHandler
- 이벤트 데이터가 없는 이벤트를 처리할 때
public class Publisher
{
// 이벤트 데이터가 없는 이벤트를 처리할 때
public event EventHandler MyEventHandler;
public void DoSometing()
{
MyEventHandler(this, EventArgs.Empty);
}
}
public class Subscriber
{
public void HanldeEvent(object sender, EventArgs e)
{
Console.WriteLine("Something happened to " + sender.ToString());
Console.ReadLine();
}
}
public class MainApp
{
static void Main(string[] args)
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
publisher.MyEventHandler += subscriber.HanldeEvent;
publisher.DoSometing();
}
}
EventHandler<EventArgs>
- 이벤트 데이터가 있는 이벤트를 처리할 때
// 이벤트 데이터
public class MyEventArgs : EventArgs
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Publisher
{
// 이벤트 데이터가 있는 이벤트를 처리할 때
public event EventHandler<MyEventArgs> MyEventHandler;
public void DoSometing()
{
MyEventArgs myArgs = new MyEventArgs();
myArgs.Id = 1;
myArgs.Name = "Kim";
MyEventHandler(this, myArgs);
}
}
public class Subscriber
{
public void HanldeEvent(object sender, MyEventArgs e)
{
Console.WriteLine("Something happened to " + sender.ToString());
Console.WriteLine("Name: " + e.Name + ", Id: " + e.Id);
Console.ReadLine();
}
}
public class MainApp
{
static void Main(string[] args)
{
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber();
publisher.MyEventHandler += subscriber.HanldeEvent;
publisher.DoSometing();
}
}
대리자를 호출하는 Invoke() 메서드 #
- 지금까지 우리는 단순히 대리자를 일반 메서드처럼 호출했지만, 본래는
Invoke()
메서드를 호출하는 것이다.
Action myAction = MyMethod;
myAction.Invoke(); // Invoke() 메서드 사용
myAction(); // 생략 가능
이벤트 구독할 때 가비지를 발생시키지 않는 방법 #
private event Action cashedAction;
private event Action myAction;
void Awake()
{
// 미리 모두 캐시해 놓고 나중에 추가한다.
cashedAction = MyMethod; // new Action(MyMethod)이므로 가비지발생
}
void Start()
{
myAction += cashedAction; // 여기서는 가비지가 발생하지 않게된다.
}
void MyMethod()
{
//...
}
중복으로 이벤트를 구독 할 위험을 줄여주는 방법 #
event
키워드가 있는 경우add{}
와remove{}
접근자를 정의할 수 있다.add{}
에서 이벤트를 구독할 때, 한번 뺀 후 추가함으로써 중복으로 구독할 위험을 줄여준다.
private event Action myAction
{
add
{
myAction -= value; // 중복으로 넣을 위험을 줄여준다.
myAction += value;
}
remove
{
myAction -= value;
}
}