안녕하세요. 오늘은 C# Delegate라는 것을 알아볼 것 입니다.


C# Delegate는 한 마디로 요약하자면 메소드(함수)를 변수로 저장하는 것 입니다.

C/C++을 해보셨던 분들에게는 함수 포인터와 비슷하다고 생각하시면 될 것 입니다.


일단 아래는 C# 델리게이트를 선언하는 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 델리게이트는 아래와 같이 선언합니다.
public delegate void NoneReturn_NoneParam_Delegate();
static void NoneReturn_NoneParam_Func()
{
    Console.WriteLine("NoneReturn_NoneParam_Func");
}
 
public delegate int IntReturn_NoneParam_Delegate();
static int IntReturn_NoneParam_Func()
{
    Console.WriteLine("IntReturn_NoneParam_Func");
    return 10;
}
 
public delegate float FloatReturn_NoneParam_Delegate();
static float FloatReturn_NoneParam_Func()
{
    Console.WriteLine("FloatReturn_NoneParam_Func");
    return 10.0f;
}
 
public delegate void NoneReturn_OneIntParam_Delegate(int i);
static void NoneReturn_OneIntParam_Func(int i)
{
    Console.WriteLine("NoneReturn_OneIntParam_Func");
}
 
public delegate int IntReturn_OnefloatParam_Delegate(float i);
static int IntReturn_OnefloatParam_Func(float f)
{
    Console.WriteLine("IntReturn_OnefloatParam_Func");
    return 0;
}
 
public delegate float FloatReturn_OneIntParam_Delegate(int i);
static float FloatReturn_OneIntParam_Func(int i)
{
    return 10f;
}
 
cs


보시면 

"접근 한정자 + delegate + 함수 타입 " 방식으로 선언합니다.


예를 들어 

2 번째 줄에 선언된 delegate의 타입 이름은 NoneReturn_NoneParam_Delegate 이며

저 타입에 들어갈 수 있는 함수의 형식은 반환 값과 매개변수가 없는 함수만 저 형식에

저장 될 수 있는 것이죠.


그럼 이제 저 delegate를 사용하는 코드를 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 선언한 델리게이트를 사용한 변수입니다.
NoneReturn_NoneParam_Delegate noneReturn_NoneParam 
    = NoneReturn_NoneParam_Func;
 
IntReturn_NoneParam_Delegate intReturn_NoneParam 
    = IntReturn_NoneParam_Func;
 
FloatReturn_NoneParam_Delegate floatReturn_NoneParam 
    = FloatReturn_NoneParam_Func;
 
NoneReturn_OneIntParam_Delegate noneReturn_OneIntParam 
    = NoneReturn_OneIntParam_Func;
 
IntReturn_OnefloatParam_Delegate intReturn_OnefloatParam 
    = IntReturn_OnefloatParam_Func;
 
FloatReturn_OneIntParam_Delegate floatReturn_OneIntParam 
    = FloatReturn_OneIntParam_Func;
 
cs


선언한 델리게이트를 사용하는 법은 일반 변수랑 같습니다.


예를들어 4 번째 줄을 보시면


NoneReturn_NoneParam_Delegate noneReturn_NoneParam 

                = NoneReturn_NoneParam_Func;


"델리게이트 타입 이름 + 변수 명 + 대입할 함수 이름" 입니다.


그럼 저렇게 변수에 저장 된 함수를 호출해볼까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 호출 방법 1
if (noneReturn_NoneParam != null)
    noneReturn_NoneParam();
 
if (intReturn_NoneParam != null)
    intReturn_NoneParam();
 
if (floatReturn_NoneParam != null)
    floatReturn_NoneParam();
 
if (noneReturn_OneIntParam != null)
    noneReturn_OneIntParam(5);
 
if (intReturn_OnefloatParam != null)
    intReturn_OnefloatParam(5f);
 
if (floatReturn_OneIntParam != null)
    floatReturn_OneIntParam(5);
 
 
 
// 호출 방법 2
// '?' 은 만약 변수가 NULL이 아니라면 뒤 함수를 호출한다는 뜻입니다.
noneReturn_NoneParam?.Invoke();
intReturn_NoneParam?.Invoke();
floatReturn_NoneParam?.Invoke();
 
noneReturn_OneIntParam?.Invoke(5);
intReturn_OnefloatParam?.Invoke(5f);
floatReturn_OneIntParam?.Invoke(5);
 
cs


호출 방법 1


함수를 저장해놓은 변수가 NULL 인지 체크하고 일반 함수를 호출하는 것과 같은 방식으로 변수에 저장되어있는 함수를 호출합니다.


항상 NULL 체크를 하는 것이 좋습니다. 만약 체크를 안하고 NULL인 델리게이트 변수의 저장된 함수를 호출하면 예외가 터집니다.


호출 방법2


이 방식은 델리게이트 타입의 변수에는 자동으로 Invoke라는 함수가 생기게 됩니다. 

Invoke를 호출하면 해당 함수를 호출하는 것이지요.


근데


변수 이름 옆에 붙은 '?' 의 의미는 해당 변수가 NULL 이 아닐 경우(변수가 초기화 된 경우) Invoke를 호출하라는 의미입니다. 간단하죠? 만약 변수가 NULL 이면 Invoke가 호출되지 않습니다. 저 '?' 같은 경우는 델리게이트 뿐만 아니라 다른 클래스에도 사용 가능 하니 한번 사용해보세요.



안녕하세요. 오늘은 Unity에서 Resource.Load로 Sprite 로딩 시 NULL이 리턴되는 문제를 다뤄볼려고 합니다.

경로가 틀려서 NULL이 리턴되는 경우는 너무 당연 한 것이기 때문에 다루지 않겠습니다.


다음 코드에 경우 리턴된 Sprite는 NULL 입니다. (Path가 올바른 경로라고 가정)


1
2
Sprite sprite = Resources.Load("Path"as Sprite;
 
cs


분명 보기에는 이상한 것이 없습니다. 


저 코드에 문제는 바로 "Resources.Load" 함수에서 Object Type으로 반환한 Sprite 객체를

"as Sprite"로 형 변환을 했기 때문입니다. 왜 그런지는 저도 잘 모르겠습니다.


그러므로 아래와 같이 수정하면 올바르게 Sprite를 반환 할 것 입니다.


1
2
Sprite sprite = Resources.Load<Sprite>("Path");
 
cs


'programing > Unity' 카테고리의 다른 글

Unity2D Sprite 실제 픽셀 사이즈와 맞추기  (2) 2019.11.03

오늘은 C#의 예외처리에 대해 알아보겠습니다. 


예외를 적절하게 사용하면 프로그램 실행 중간에 에러가 터져도 프로그램이 꺼지지 않고 넘길 수 있습니다. 하지만 굳이 예외가 필요없는 곳인데도 불구하고 과도하게 사용 할 경우 성능 저하를 불러 올 수 있습니다.


다음과 같은 형식으로 예외 처리를 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Main(string[] args)
{
    // try 문 안에서 예외를 던져야 예외를 catch 할 수 있습니다.
    try
    {
        // 생성자에 Message를 넣을 수 있습니다.
        throw new Exception("예외를 던집니다.");
    }
    // try 문 안에서 throw 된 예외를 catch 문에서 받습니다.
    catch (Exception ex)
    {
        // 예외 클래스의 Message 변수로 예외처리 
        //변수의 생성자에 넣은 메시지를 얻어 올 수 있습니다.
        Console.WriteLine(ex.Message);
    }
}
 
cs


출력 결과.


예외를 던집니다.

계속하려면 아무 키나 누르십시오 . . .


만약 throw 뒤에 다른 코드가 있다면 어떻게 될까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Main(string[] args)
{
    // try 문 안에서 예외를 던져야 예외를 catch 할 수 있습니다.
    try
    {
        throw new Exception("예외를 던집니다.");
 
        Console.WriteLine("출력");
    }
    // try 문 안에서 throw 된 예외를 catch 문에서 받습니다.
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}
 
cs


출력 결과.


예외를 던집니다.

계속하려면 아무 키나 누르십시오 . . .


출력 결과를 보면 알 수 있듯이 try문 내에 예외가 던져진 곳 다음 코드는 실행이 안되는 것을 알 수 있습니다.


다음과 같은 상황에서는 어느 catch문이 실행될까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void TestFunc()
    try
    {
        throw new Exception("예외");
    }
    catch (Exception ex)
    {
        Console.WriteLine("TestFunc " + ex.Message);
    }
 
}
 
static void Main(string[] args)
{
    // try 문 안에서 예외를 던져야 예외를 catch 할 수 있습니다.
    try
    {
        // 예외처리 구문이 있는 함수를 호출합니다.
        TestFunc();
    }
    // try 문 안에서 throw 된 예외를 catch 문에서 받습니다.
    catch (Exception ex)
    {
        Console.WriteLine("MainFunc" + ex.Message);
    }
}
 
cs


출력 결과.


TestFunc 예외

계속하려면 아무 키나 누르십시오 . . .


TestFunc 함수 내에 예외가 출력되었습니다. 결과를 보면 가장 가까운 catch (가까운 스택) 문에 점프하는 걸 볼 수 있습니다. 


근데 제가 처음에 너무 과도하게 사용하면 성능 저하가 있을 수도 있다고 적어놨습니다. 왜 그럴까요?


아래 코드를 봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static void TestFunc01()
{
    TestFunc02();
 
}
 
static void TestFunc02()
{
    TestFunc03();
 
}
 
static void TestFunc03()
{
    TestFunc04();
}
 
static void TestFunc04()
{
    throw new Exception("예외");
}
 
 
static void Main(string[] args)
{
    // try 문 안에서 예외를 던져야 예외를 catch 할 수 있습니다.
    try
    {
        // 예외처리 구문이 있는 함수를 호출합니다.
        TestFunc01();
    }
    // try 문 안에서 throw 된 예외를 catch 문에서 받습니다.
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}
 
cs


위 코드의 콜 스택은 어떻게 될까요.


예외 발생 전

TestFunc01 -> TestFunc02 -> TestFunc03 -> TestFunc04 -> 예외 발생!


예외 발생 후

Test04 -> Test03 -> Test02 -> Test01 -> catch 문!.


예외가 발생하면 catch문이 있는 곳 까지 콜 스택이 되돌려집니다.

물론 위와 같은 코드는 한 번만 실행되기 때문에 성능 저하가 없을 수도 있습니다.


하지만 


서버 같이 항상 실행되는 경우 저런 예외처리 구문이 많으면 성능 저하가 생길 수 도 있습니다.


단 많은 .Net Framework에서는 예외처리를 사용하여 에러를 확인해 할 때가 있습니다.

예를 들면 C# System.Net에 서버 API들은 대부분의 오류를 예외처리로 던져줍니다. 


그렇기 때문에 신중하게 사용하는 것이 중요합니다.



+ Recent posts