1-1. delegate
- 메서드를 담을 수 있는 자료형.
- 참조 타입
- 콜백함수, 지정자, 이벤트(앞에 event 붙여서)로서 사용할 수 있다.
1-3. 사용 방법
public delegate float MyDel(float left, float right);
public static float Plus(float left, float right) { return left + right; }
static void Main()
{
MyDel del;
del = Plus;
Console.WriteLine(del(2.5f, 3.5f));;
}
- 담으려는 메서드의 매개변수 타입과 리턴타입이 delgate와 일치해야한다.
1-4. Func, Action, Predicate
Func<float, double, int> fun1 = Function;
Action<float, double> fun2 = Function2;
Predicate<int> p = IsCheck;
public static int Function(float a, double b) { return 0; }
public static void Function2(float a, double b) { }
public bool IsCheck(int value)
{
return true;
}
- 위와 같이 따로 delgate 변수를 선언하지 않아도 바로, Func와 Action을 이용해서 메서드를 담기 가능.
- Func과 Action 둘다 맨 뒤의 매개 변수는 return타입이다.
- Func은 return타입으로 void형이 불가능, Action은 return 타입으로 void형이 가능.
- Predicate는 단일 매개 변수를 가지고,return 타입으로 void형을 가짐.
1-5. delegate 체인
public static void Hello()
{
Console.WriteLine("Hi");
}
public static void Bye( )
{
Console.WriteLine("Bye");
}
static void Main()
{
MyDel del;
del = Hello;
del += Bye;
del();
}
- 델리게이트는 하나의 메서드뿐만 아니라 여러 개의 메서드를 저장가능.
1-6. delegate 호출 방법
del();
del?.Invoke();
- 기존 함수처럼 사용할 수도 있지만, delegate에 저장된 메서드가 없다면 runtime error가 발생가능.
- ?.Invoke() 식으로 사용하면, null이 아닌 경우에만 실행.
1-7. 콜백함수
- 델리게이트를 이용하여 특정조건에서 반응하는 함수를 구현.
- 함수의 호출이 아닌 역으로 호출받을 때 반응을 참조하여 역호출
-> 즉, 조건이 충족하는 지 여러번 확인하고, 충족될 때 함수가 스스로 호출하는 것이 아닌, 조건이 충족될 때 함수가 역으로 호출을 받는 것이다.
void Main()
{
File file = new File();
Button saveButton = new Button();
saveButton.callback = file.Save;
Button loadButton = new Button();
loadButton.callback = file.Load;
saveButton.Click(); // output : 저장하기 합니다.
loadButton.Click(); // output : 불러오기 합니다.
}
public class Button
{
public Action callback;
public void Click()
{
if (callback != null)
{
callback();
}
}
}
public class File
{
public void Save()
{
Console.WriteLine("저장하기 합니다.");
}
public void Load()
{
Console.WriteLine("불러오기 합니다.");
}
}
}
- Button의 Click()메서드에서 등록된 콜백함수 Save()와 Load()가 호출 된다.
1-8.지정자
- 델리게이트를 사용하여 미완성 상태의 함수를 구성.
- 매개변수로 전달한 지정자를 기준으로 함수를 완성하여 동작시킴.
- 기준을 정해주는 것으로 다양한 결과가 나올 수 있는 함수를 구성가능.
void Main()
{
int[] array = { 3, -2, 1, -4, 9, -8, 7, -6, 5 };
int index1 = CountIf(array, IsPositive); // 배열 중 값이 양수인 데이터 갯수
int index2 = CountIf(array, IsNagative); // 배열 중 값이 음수인 데이터 갯수
int index3 = CountIf(array, value => value > 5); // 배열 중 값이 5보다 큰 데이터 갯수
}
public static int CountIf(int[] array, Predicate<int> predicate)
{
int count = 0;
for (int i = 0; i < array.Length; i++)
{
if (predicate(array[i]))
{
return count++;
}
}
return count;
}
public static bool IsPositive(int value)
{
return value > 0;
}
public static bool IsNagative(int value)
{
return value < 0;
}
1-9. event키워드와 delegate
- player.Hit과 같이 event delegate를 생성한 클래스 내부에서만 호출이 가능하다.
2-1. 익명 함수란
- 이름이 없는 함수.
- 주로 재사용 될 여지가 없는 기능 등, 일회성 사용을 위해 메서드가 선언되는 경우 사용하는 기능.
- 코드 내용이 간결하고 직관적이지만, 남용되는 경우 코드 리뷰와 이해가 오랜 시간이 걸리고 디버깅이 어려울 수 있다.
2-2. 사용 방법
internal class Program
{
public delegate void VoidDel();
public delegate int IntDel(params int[] values);
public static VoidDel voidDel;
public static IntDel intDel;
public static void Main()
{
//이름이 없기 때문에 delegate를 통하지 않으면 함수를 프로그램내에 실행할수 없다
voidDel += delegate ()
{
Console.WriteLine("익명 함수!");
};
intDel += delegate (int[] values)
{
int result = 0;
foreach (int v in values)
{
result += v;
}
return result;
};
//람다식선언
voidDel += () => Console.WriteLine("Lamda 선언!");
intDel += (values) =>
{
int result = 0;
foreach (int i in values)
{
result += i;
}
return result;
};
voidDel?.Invoke();
Console.WriteLine(intDel?.Invoke(5, 4, 2, 4, 1));
}
}
public static class Util
{
public static void PrintLog()
{
Console.WriteLine("Print Log");
}
public static int Sum(params int[] values)
{
int result = 0;
foreach(int v in values)
{
result += v;
}
return result;
}
public static void CreateClosure()
{
int count = 0;
Program.voidDel += () =>
{
count++;
Console.WriteLine(count);
};
}
}
- delegate 방식선언과 람다식 방식 선언이 존재.
2-3. Closure
internal class Program
{
public delegate void VoidDel();
public delegate int IntDel(params int[] values);
public static VoidDel voidDel;
public static IntDel intDel;
public static void Main()
{
Util.CreateClosure();
voidDel?.Invoke();
voidDel?.Invoke();
voidDel?.Invoke();
voidDel?.Invoke();
}
}
public static class Util
{
public static void PrintLog()
{
Console.WriteLine("Print Log");
}
public static int Sum(params int[] values)
{
int result = 0;
foreach(int v in values)
{
result += v;
}
return result;
}
public static void CreateClosure()
{
int count = 0;
//클로저
Program.voidDel += () =>
{
//캡쳐
count++;
Console.WriteLine(count);
};
}
}
- 위 코드 처럼, CreateClosure()을 호출하고 종료되면
그 안에 count 변수는 메모리에서 해제가 될것이다.
- 여기서 문제는 할당될 count변수를 익명함수가 참조하고 있다.
- 에러가 발생할거라 예상할 수 있지만, 에러가 발생하지 않는다.
-> 외부의 변수나 객체를 익명함수에서 참조할 경우,
해당 변수의 생명주기는 익명함수가 더 이상 호출되지 않을 때까지로 연장.
-> 참조하는 변수와 익명 함수를 임시 클래스로 만들어 heap메모리에 할당하기 때문!
-> 해당 익명함수 자체를 클로저라고 부르고, 클로저가 변수를 캡쳐 했다고 표현
- 실수에 의해 지속적으로 closure가 누적되면 메모리 누수가 발생할 수 있으니 조심할 필요가 있음!