[C#] 익명 메소드


C#에서는 익명 메소드라는 것이 있는데

말그대로 이름이 없는 메소드이다.


어떻게 이름이 없는 메소드가 있을까?

생각할 수 있지만 델리게이트를 통해

선언하고 사용될 수 있다. (밑의 예제 참고)




위의 궁금증보다 사실 이런 메소드를

대체 어디다 쓰려는 것일까? 라는 의문이 드는데

익명 메소드는 람다식에서 아주 유용하게

쓰인다고 하니 람다식을 공부하면서 좀 더

깊게 공부해보는 시간을 가지도록 하겠다.




delegate int Calculate (int a , int b);


public static void Main(){

Calculate Calc;

Calc = delegate ( int a, int b ){

return a + b;

}


Console.WriteLine("3+4 : {0}", Calc(3,4);

}




익명 메소드는 delegate 키워드를 이용하여 선언한다.

익명 메소드는 자신을 참조할 델리게이트의 형식과 동일해야 한다.

익명 메소드는 매개 변수의 형식, 개수까지도 동일해야 한다.


델리게이트가 참조할 메소드를 넘겨야 할 일이 생겼는데,

이 메소드가 두 번 다시 사용할 일이 없다고 판단 되면 그 때

사용하는 것이 익명 메소드란다. 매번 사용되고 복잡한

메소드라면 익명 메소드를 사용하지 않는 것이 바람직할 것이다.

(아직은 익명 메소드에 대해 감이 잘 잡히지 않는다.)




아래의 익명 메소드 예제를 보면서 포스팅을 마치도록 하겠다.




이렇듯 익명 메소드는 구현부가 항상 따라다니기 때문에

여러번 사용될 메소드라면 꽤나 번거로울 것이다. 그리고

복잡한 내용의 메소드라면 가독성을 해칠 수 있기 때문에

이 경우에도 사용하지 않는 것이 좋을 것이다. 이 익명 메소드는

후에 공부할 람다식에서 다시 자세하게 알아보도록 하겠다.


[C#] 일반화 델리게이트


델리게이트는 보통의 메소드뿐 아니라

일반화 메소드도 참조할 수 있다.

물론 이경우에는 델리게이트도 일반화

메소드를 참조할 수 있도록 형식 매개 변수를

이용해서 선언을 해주어야 한다.


delegate int Compare<T>(T a, T b);




그 전 포스팅에서 다루었던

오름차순 내림차순 함수를 참조하는 델리게이트와

이를 매개 변수로 받아서 정렬 방식을 결정하는

버블 소트의 예제를 일반화 버전으로 바꾸어 보겠다.




결과




일반화된 델리게이트로 수정했다.

위 부분에서 IComparable<T>를 상속받는부분이 있는데

이 부분에 대해서 좀 짚고 넘어가 보자.


System.Int32Pint), System.Double(double)을 비롯한

모든 수치 형식과 System.String(string)은 모두

IComparable을 상속해서 CompareTo() 메소드를구현하고 있다. 

CompareTo() --> 자신 보다 크면 -1, 같으면 0, 작으면 1을 반환




위의 코드에서 where T : IComparable<T>는

T가 무조건 IComparable<T>를 상속하는 파생클래스여야 한다는 뜻이다.

때문에 T는 CompareTo() 라는 메소드를 쓸 수 있어야 한다.




일반화된 델리게이트에 대해 알아보았다.

형식 매개 변수 제약에 관한 내용이 잠깐 나왔는데

이 부분에 대해서는 나중에 일반화 프로그래밍을

포스팅할 때 다뤄보도록 하겠다.




[C#] 델리게이트 (delegate)


델리게이트란?

델리게이트는 메소드에 대한 참조이다.

델리게이트에 메소드의 주소를 할당한 후

델리게이트를 호출하면 이 델리게이트가

메소드를 호출해준다.




언제 사용하는가?

프로그래밍을 하다 보면 '값'이 아닌 '코드'를 매개 변수로 넘기고

싶을 때가 있다. 배열을 정렬하는 메소드를 예로 들자면,

이 메소드가 오름차순으로 정렬할 것인지, 내림차순으로

정렬할 것인지, 아니면 특별한 계산식에 의해 정렬할 것인지

이 메소드가 정렬을 수행할 때 사용하는 비교 루틴을

매개 변수로 넣을 수 있다면 아주 유연한 프로그램이 될 것이다.

이에 대한 예시는 밑에서 다시 다루도록 하겠다.




C++ STL의 알고리즘에 함수 포인터를

이용한 콜백 메커니즘이 사용되는데

델리게이트 또한 이 함수포인터와

유사하다고 생각하면 될 듯하다.




한정자 delegate  반환형식 델리게이트 이름 ( 매개변수 목록 );

ex) delegate int MyDelegate ( int a , int b);


--> 참조할 메소드의 반환 형식과 매개 변수를 명히해줘야 한다.

--> 델리게이트는 인스턴스가 아닌 형식이다.

--> 따라서 MyDelegate의 인스턴스를 따로 만들어야 한다.




ex)

int Plus (int a, int b){ ... }

MyDelegate CallBack;

CallBack = new MyDelegate(Plus);

--> new 연산자를 사용하여 인스턴스를 만든다.




ex)




결과




오늘은 C#의 델리게이트(delegate)에 대해 알아보았다.

그냥 함수 포인터인 것 같다. (아직까지는..)

다음 시간에는 일반화된 델리게이트에 대해 알아보겠다.




C# 인덱서 (Indexer)


인덱서는 인덱스를 이용해서 객체 내의 데이터에

접근하게 해주는 프로퍼티라고 생각하면 된다.

객체를 마치 배열처럼 사용할 수 있게 해준다.


한정자 인덱서 형식 this [형식 Index]{

get{ ... }

set{ ... }

}



C# 인덱서 예제




foreach가 가능하게 만들어보자.

foreach 문은 IEnumerable과 IEnumerator를

상속하는 형식만 지원한다. 따라서 IEnumerable과

IEnumerator를 상속하기만 하면 foreach를 이용할 수 있다.




IEnumerable의 메소드

1. IEnumerable GetEnumerator()


IEnumerator 인터페이스를 상속하는

클래스의 객체를 반환하면 된다.

그렇다면 IEnumerator 인터페이스는 무엇일까.




IEnumerator 메소드 및 프로퍼티

1. boolean MoveNext()

2. void Reset()

3. Object Current { get; }


자 그럼 위의 예제를 foreach가 가능하도록 만들어 보자.




IEnumerator 멤버




IEnuemerable 멤버




메인




C#의 인덱서 (Indexer)에 대해 알아보았다.

C++이랑 용어만 다르지 아직까진 비슷한게 더 많은듯




[C#] 상속 관계에 있는 클래스의 형변환

is 연산자와 as 연산자




C++의 다운 캐스팅과 연관된 연산자인 듯 하다.

예전 dynamic_cast<>를 공부할 때 비슷한 내용을

다뤘던 것 같다. C#에서는 좀 더 세련된(?) 형변환을

위해 멋진 연산자 두 개를 제공한다. 바로 is와 as




class Mammal{

public void Nurse() { ... }

}


class Dog : Mammal {

public void Bark() { ... }

}


class Cat : Mammal {

public void Meow() { ... }

}


익히 알고 있듯이 포유류는 포유류 이고

개도 고양이도 모두 포유류이다.


Mamal m = new Mammla();

m.Nurse();


m = new Dog();

m.Nurse();


Dog dog = (Dog)m;     // 다운 캐스팅이다.

dog.Nurse();                // 하지만 m이 가리키고 있는 것이

dog.Bark();                  // Dog이기 때문에 가능하다.


m = new Cat();

m.Nurse();


Cat cat = (Cat)m;        // 역시 다운 캐스팅.

cat.Nurse();                // 하지만 m이 가리키고 있는 것이

cat.Meow();                // Cat이기 때문에 가능하다.




위의 코드를 is 와 as 연산자를 사용해서 바꾸어보자.

먼저 is와 as를 간단하게 설명하자면


is

객체가 해당 형식에 해당하는지 검사하여

그 결과를 bool 값으로 반환한다.


as

형식 변환 연산자와 같은 역할을 한다.

다만 형변환 연산자가 변환에 실패하는 경우에

예외를 던지는 반면, as 연산자는 객체 참조를

null로 만들어 버린다. (dynamic_cast와 같은 성질)




Mammal m = new Dog();

Dog dog;


if( m is Dog ){                    // 말그대로  m 이 Dog를 가리키고 있니?

dog = (Dog)m;

dog.Bark();

}


Mammal m2 = new Cat();

Cat cat = m2 as Cat;         // m2를 Cat으로 형변환하라.

if( cat != null ){                 // 다운 캐스팅을 하라는 말인데

cat.Meow();             // 이 경우 m2는 Cat를 가리키고 있기 때문에 가능하다.

}                                      // 만약 m2 = new Mammal() 이었다면 불가능.

 // 파생 클래스는 기본 클래스보다 더 많은 정보를 담고 있는데

 // 기본 클래스에 없는 부분을 호출하려 한다면 문제가 발생.

// 때문에 이 경우 cat는 null이 된다.




C++의 시점에서 설명이 된 듯한데

확실히 C++의 dynamic_cast<> 보다는

훨씬 사용법이 간편한 것 같다.




일반적으로 형식 변환 연산자 대신 as 연산자를

사용하는 쪽이 더 권장 되는데, 형식 변환에 실패하더라도

예외가 일어나 갑자기 코드의 실행이 갑자기 점프하는 일이

없으므로 코드 관리가 더 수월해지기 때문이란다.


주의해야할 점은 as 연산자는 참조 형식에 대해서만

사용이 가능하므로 값 형식의 객체는 기존의 형식

변환 연산자를 사용해야 한다.





01. 모바일 2D 디펜스 게임

'Wings Defense'


포트폴리오 링크 :

https://www.youtube.com/watch?v=bB1_zYog1o0


디펜스 게임의 긴장감과 RPG 게임의 성장에

초점을 맞추어 제작한 디펜스 게임입니다.

다양한 스킬 업그레이드과 캐릭터의 업그레이드 중

어느 쪽에 적절한 투자를 하느냐가 게임의 핵심입니다.


제작 기간 : 16.06.23 ~ 16.08.05 (약 5주)





02. 모바일 스릴러 TPS 게임

'Red Sniper'


포트폴리오 링크 :

https://www.youtube.com/watch?v=OiL2Yv6L73Q


스릴러 장르에 저격이라는 요소를 넣어

제작한 모바일 TPS 게임입니다.

저격수답게 적에게 들키지 않아야하며

신중하게 조준하여 적을 처치해야 합니다.


제작 기간 : 16.08.16 ~ 16.09.13 (약 4주)







유니티 C# : C#의 깊은 복사 동작


C++ 에서도 얕은 복사와 깊은 복사 동작이 있었다.

기본 복사 생성자에 의해 얕은 복사가 진행되어

깊은 복사 동작은 우리가 직접 정의해 줬어야했다.




C#도 클래스의 기본 복사동작은 얕은 복사를 하고 있는데

깊은 복사를 정의하는 과정과 사용방법이 C++과

다른 점이 있어 포스팅을 하게 되었다.




우선 C#은 값 형식과 참조 형식으로 나누어지는데

클래스의 인스턴스는 태생부터 참조 형식이다.

때문에 단순히 대입을 하여 복사를 할 경우,

힙에 있는 인스턴스가 복사가 되는 것이 아니라

스택에 이 인스턴스를 가리키는 놈이 하나 더 생성된다.

(요기 까지는 C++의 문제점과 다를게 없다.)




자 그럼 간단하게 코드를 작성해보자.


class MyClass{


public int MyField1;

public int MyField2;


public MyClass DeepCopy(){


MyClass newCopy = new MyClass();

newCopy.MyField1 = this.MyField1;

newCopy.MyField2 = this.MyFiedl2;


return newCopy;

}

}


class Main{

static void main(string[] args) {


MyClass source = new MyClass();

source.MyField1 = 10;

source.MyField2 = 20;


MyClass target = source.DeepCopy();

target.MyField = 30;


}

}



이제 위의 예에서는 깊은 복사가 진행되어

source와 target이 힘에 서로 다른 객체를 가리킬 수 있다.

따라서 우리가 예상했던 결과가 나올 것이다.




또 하나 중요한 부분은 ICloneable.Clone() 메소드이다.

.NET 프레임워크의 System 네임스페이스에는 ICloneable

이라고 하는 인터페이스가 있다. 깊은 복사 기능을 가질

클래스가 .NET 프레임워크의 다른 유틸리티 클래스나

다른 프로그래머가 작성한 코드와 호환되도록 하고 싶다면

ICloneable을 상속하도록 하는 것이 좋다. ICloneable

인터페이스는 Clone() 메소드 하나만을 가지고 있다.




class MyClass : ICloneable{


public int MyField1;

public int MyField2;


public Object Clone(){

MyClass newCopy = new MyClass();

newCopy.MyField1 = this.MyField1;

newCopy.MyField2 = this.MyFiedl2;


return newCopy;

}

}



이제 이 클래스는 .NET 프레임워크와 호환성을 가지게 된다.

(Object로 반환 받을 수 있는 이유는 C#의 모든 데이터 형식은

Object를 상속받기 때문 : 전 포스팅 박싱/언박싱 참조)



C# 기초 : 가변길이 매개 변수 , 명명된 매개 변수




C++를 하다가 넘어 왔는데, 다른 부분이 조금씩 보이기 시작

가변길이 매개 변수에 대해 먼저 알아보도록 하자.




1. 가변길이 매개 변수

함수를 오버로딩하는 과정에서

똑같은 타입인데 매개변수의 '수'가

다르다는 이유만으로 오버로딩을 했다.

C#은 가변길이 매개 변수를 제공하여

그 개수가 유연하게 변할 수 있게 한다.




int total = 0;


total = Sum( 1, 2, 3);

total = Sum( 1, 2, 3, 4, 5);

total = Sum( 1, 2, 3, 4, 5, 6, 7, 8);


위와 같이 매개 변수의 수가 정해져 있지 않다면

(똑같은 타입일 경우에만) 그 만큼 함수를 오버로딩해서

사용하는 것이 아니라 가변길이 매개 변수를 사용한다.




가변길이 매개 변수는 params 키워드와 배열을 이용해서 선언.


int Sum ( params int[] args ){


int sum = 0;


for( int i = 0; i<args.Length; i++){

sum += args[i];

}


return sum;

}


==> 형식은 같으나 매개 변수의 개수만 유연하게 달라질 경우에 적합하다.




2. 명명된 매개 변수


메소드를 호출할 때 매개 변수 목록 중 어느 매개 변수에 데이터를

할당할 것인지를 지정하는 것은 '순서'이다. 명명된 매개 변수는

순서가아닌 매개 변수의 이름에 근거해서 데이터를 할당한다.




static void Print( string name , int phone ){ ... }


static void Main(string[] args){

Print ( name : "홍길동" , phone : "2222" );

Print ("김사랑" , "11111" );

Print ( phone : "3333" , name : "한고은" );

}




다소 번거로워 보일 수도 있지만, 매개 변수의 수가 많은 경우

가독성을 높여줄 수 있다. 또한 디폴드 매개 변수가 많은 경우에도

가독성이 상당히 안좋아질 수가 있는데, 명명된 매개 변수를

사용할 경우 이 문제를 해결할 수 있다.









C# 기초 : (C++ 에서 참조자라 불리는) ref 키워드

그리고 out 키워드 (출력 전용 매개 변수)




C++에서 레퍼런스(참조자)는 큰 성능향상을 가져다 주었다.

상수 객체 참조자에 의한 전달 방식이 그 예이다.

call by refference 방식을 구현할 때에도 포인터를

이용하는 것이 아닌 레퍼런스를 이용해 보다 안전하고

간편하게 구현할 수 있었다. C# 또한 마찬가지




하지만 다른점이 있는데

&가 아닌 ref를 붙여줘야 한다는 점


void swap( ref int a, ref int b){

...

}


사용할 떄에도


int x;

int y;


swap(ref x , ref y);




사용할 때에도 왜 귀찮게 ref를 붙여줄까..

생각해봤는데, C++에서 swap함수를 참조자로

만들었다면 사용하는데 있어 이 함수가

call by value를 하는 것인지

call by refference를 하는 것인지

분간이 안간다는 것이다.


하지만 C#에서는 ref를 붙여줌으로써

구분이 가능하다. (단순히 내 생각..)




out 키워드 (출력 전용 매개 변수)

out 키워드는 ref 키워드와 똑같은 일을 하지만

좀 더 안전한 방법으로 일을 한다.




void Divide ( int a , int b, ref int quotient , ref int remainder ){

quotient = a / b;

remainder = a % b;

}


int main(){

int a = 20 , b = 3, c = 0, d = 0;


Divide( a, b, ref c, ref d);

}


a를 b로 나눈 뒤 나머지와 몫을 구해서 c 와 d에 저장한다.

위 코드를 out 키워드로 바꾸어 보자.


void Divide ( int a , int b, out int quotient , out int remainder ){

quotient = a / b;

remainder = a % b;

}


int main(){

int a = 20 , b = 3, c = 0, d = 0;


Divide( a, b, out c, out d);

}




결과는?

ref 키워드와 똑같다. 그렇다면 out 키워드는 왜 쓰는가?




ref 키워드를 이용해서 매개 변수를 넘기는 경우에는 메소드가

해당 매개 변수에 결과를 저장하지 않더라도 컴파일러는

아무런 경고를 하지 않는다. 이와 달리 out 키워드를

이용해서 매개 변수를 넘길 때는 메소드가 해당 매개 변수에

결과를 저장하지 않으면 컴파일러가 에러 메시지를 출력한다.




또한 메소도를 호출하는 쪽에서는 초기화를 하지 않은

지역 변수를 메소드의 out 매개 변수로 넘기는 것이 가능.

컴파일러가 호출당하는 메소드에서 그 지역 변수를

할당할 것을 보장하기 때문이다.




즉, 런타임 에러를 컴파일 타임 에러로 잡아낼 수 있다는 장점이 있다.



유니티 C# : 박싱과 언박싱




C#은 object가 모든 데이터를 다룰 수 있도록 하기 위해

모든 데이터 형식, 심지어 프로그래머들이 만드는

데이터 형식마저도 자동으로 object 형식을 상속 받는다.

즉, object는 모든 데이터 형식의 base class가 된다.




object 형식은 참조 형식이기 때문에 힙에 데이터를 할당한다.

반면, int나 double은 값 형식이기 때문에 스택에 데이터를

할당한다. 하지만 앞서 모든 데이터는 object를 상속받는다고

했는데 어떻게 값 형식의 데이터를 object에 담을 수 있는가?




object 형식은 값 형식의 데이터를 힙에 할당하기 위해

박싱 (boxing) 기능을 제공한다.


object a = 20;


20은 힙에 할당 되고, a는 그 주소를 참조한다. (박싱)




반대로 힙에 있던 값 형식 데이터를 값 형식 객체에

다시 할당해야 하는 경우가 있는데

이를 언박싱(unboxing)이라고 한다.


object a = 20; //// 박싱

int b = (int)a //// 언박싱


a는 박싱되어 20이 저장되어 있는 힙을 참조하고 있다.

b는 a가 참조하고 있는 메모리로부터 값을 복사한다. (언박싱)




정리하자면

박싱 : 값 형식을 참조 형식으로 변환

언박싱 : 참조 형식을 값 현식으로 변환




박싱의 과정 : 

1. 값 타입을 힙에 생성하기 위해 메모리를 힙 영역에 생성

2. 값을 힙 영역에 할당된 메모리로 복사

3. 참조할 변수에 할당된 메모리 주소를 할당




언박싱의 과정 :

1. 박싱값인지 확인

2. 박싱된 값이라면 값 타입 변수에 복사

3. 박싱한 메모리와 언박싱한 메모리 2개 존재 ( 가비지 발생 )


주의 : 모든 객체가 값 형식으로 언박싱이 될 수 없고, 이전에 박싱이 된

데이터에 한하여 언박싱이 가능하다. 또한 박싱하기전의 타입을 따라야 한다.




박싱/언박싱(boxing/unboxing)

MSDN 에 의하면 값 형식을 박싱할 때에는 완전히 새로운

개체가 만들어져야 하며, 이러한 작업에는 할당 작업보다

최대 20배의 시간이 걸린다고 한다. 언박싱 또한 캐스팅

과정이 할당 작업보다 4배의 시간이 걸릴 수 있다고 나와있다.




보시다시피 엄청난 성능상의 단점이 있다.

하지만 박싱/언박싱의 편리성 때문에 간혹 쓰이기도 한다.

--> ArrayList , 헤쉬테이블이 박싱/언박싱의 대표적인 예이다.


성능상의 단점을 감수하면서까지 쓸 필요는 없다고 생각한다.

리스트나 딕셔너리로도 충분히 대체할 수 있기 때문에

이를 공부하여 적절히 사용하는 방법이 좋을 것 같다.




+ Recent posts