지난 포스팅 인터페이스에서 잠시 언급된 추상과 가상에 대한 이야기를 하도록 하겠습니다. 추상과 가상을 먼저 이야기 했으면 좋았겠지만, 제 포스팅은 그냥 의식의 흐름으로 적는 포스팅이기 때문에 순서가 엉망입니다. 우선 인터페이스에 대한 이야기가 궁금하시면 아래 포스팅을 참고해주세요.

https://math-development-geometry.tistory.com/39

 

C# - interface 인터페이스 사용법과 원리

인터페이스는 상속 받을 클래스에 기능이나 속성을 지정하는 역할을 합니다. 일반적인 상속관계와 비슷하다고 볼 수 있지만 살짝 다른 개념입니다. 우선 C#에서는 C++과 다르게 하나의 부모 클래스만 상속 받을 수..

math-development-geometry.tistory.com

우선 추상은 키워드 abstract를 사용합니다. 클래스나 함수, 속성, 인덱서 앞에 제한자로 사용이 가능합니다. 가상은 키워드 virtual을 사용하고 함수나 속성, 인덱서 앞에 사용할 수 있습니다. 여기서 abstract와 virtual은 대부분 비슷하게 사용되지만 차이점은 구현체의 존재 유무입니다. 

정리하면 abstract가 사용가능한 범위는 함수, 속성, 인덱서, 이벤트, 클래스

virtual은 함수, 속성, 인덱서, 이벤트에서 사용 가능합니다.

자, 이제 사용 범위에 대해서 알아봤는데요. 큰 차이점은 구현체의 유무라고 했는데요. 쉽게 이야기하면 abstract으로 선언된 것이 있는 경우 해당 위치에서 명시적으로 구현이 된 것이 아니고, 상속 받은 위치에서 구현을 하도록 맡기는 것입니다. 그리고 상속 받은 클래스에서는 abstract를 구현하지 않으면 에러가 나기 때문에 클래스를 구조화할 경우 해당 클래스를 상속 받아서 필수적으로 구현해야하는 것과 아닌 것을 나눌 수 있는데요. 여러명이 같은 프로젝트를 할때, 다른 사람의 의도를 정확히 알기 어렵기 때문에 이런 기능을 이용하면 해당 클래스를 상속 받아서 사용하는 프로그래머 입장에서는 따로 설명을 하지 않아도 이를 인지할 수 있게 됩니다. 이와 반대로 virtual은 상속 받은 클래스에서 따로 구현을 할 필요가 없습니다. 필요할 경우에만 구현을 하게 됩니다. 

그럼 예를 들어서 abstract와 virtual이 어떻게 사용되는지 알아보도록 하겠습니다. 이번에는 게임 캐릭터를 예로 들어보겠습니다. (제 머리 속에는 LoL 을 생각하고 글을 쓰고 있어요. )

일반적으로 A를 누르면 공격이고 S를 누르면 멈추는 기능이 있죠? 이런 공통적인 기능은 virtual로 구현하면 됩니다. 모든 캐릭터가 공격과 멈추는 기능은 같기 때문입니다. 하지만 QWER와 같은 스킬은 전부 다르죠. 그런 경우 abstract으로 구현합니다. 왜냐면 모든 케릭터는 QWER에 스킬 3개와 궁극기가 있기 때문이죠. 코드를 보면서 이야기를 정리해보도록 하겠습니다.

BaseCharacter라는 클래스는 이렇게 구현할 수 있겠습니다. 물론 Attack() 과 Stop() 은 virtual 이 아닌 일반 함수로 구현하여도 상관없지만, 기본 공격동작 외에 다른 동작을 추가하고 싶다면 각 캐릭터마다 필요한 동작을 추가하면 됩니다. 그리고 나머지 abstract으로 정의된 함수들은 실제로 BaseCharacter에서 구현하지 않고 이를 상속한 클래스에서 구현하게 됩니다. 

이런 식으로 Character A를 구현할 수 있습니다. override 된 함수에 각 캐릭터마다 특별한 스킬을 구현해주면 한개의 캐릭터가 완성되는 것이죠. 이번에는 Character B를 구현해볼께요. 이때 Attack()과 Stop() 함수를 override 해보도록 하겠습니다.

CharacterA에 비해서 CharacterB는 Attack()과 Stop()을 override 하여 새로운 기능을 추가해주었습니다. 그리고 base.Attack()과 base.Stop()함수는 상속 받은 클래스의 구현된 함수로 들어가기 때문에 기존에 구현한 기능은 그대로 수행하게 됩니다. 만약에 base 함수를 지우게 되면 파생 클래스에서 구현한 동작은 실행되지 않습니다. 만약에 기존의 Attack() 기능을 사용하지 않고 완전 새로운 기능을 추가하고 싶다면 base 함수를 삭제하여도 되는 것이죠.

인터페이스는 상속 받을 클래스에 기능이나 속성을 지정하는 역할을 합니다. 일반적인 상속관계와 비슷하다고 볼 수 있지만 살짝 다른 개념입니다. 우선 C#에서는 C++과 다르게 하나의 부모 클래스만 상속 받을 수 있습니다. 따라서 여러가지 기능들을 사용할 경우 하나의 부모 클래스에서 기능을 상속 받아 자식 클래스에서 정의를 하게 됩니다. 

하지만 이런 경우 부모 클래스에 너무 많은 기능이 들어가게 되고, 이런 경우 부모 클래스가 무슨 기능을 하는 클래스인지 명확하게 구분하기 어려워집니다. 그렇기 때문에 C#에서는 인터페이스를 이용하여 기능을 추가적으로 구현할 수 있게 도와줍니다. 

예를 들면 자동차라는 클래스가 부모 클래스이고, 자동차라는 클래스를 상속 받아서 여러 회사의 자동차를 만든다고 가정해보겠습니다. 부모 클래스인 자동차에는 기본적인 기능만 들어가게 됩니다. 예를 들면 가속, 감속, 핸들 조종과 같은 자동차가 가져야할 기본적인 기능들이 자동차 클래스에 있는 것입니다. 

여기에 각 회사별로 특별한 기능들이 있겠죠? 이런 기능들은 인터페이스로 정의를 하는 것입니다. 예를 들면 크루즈 모드는 라는 인터페이스를 만들어서 A, B라는 회사에서 나오는 자동차들은 크루즈 모드가 있기 때문에 크루즈 모드 인터페이스를 상속 받게 됩니다. 또 B, C라는 회사에서 출시되는 자동차에는 자율주행이라는 기술이 들어가 있다고 하면, 자율 주행 인터페이스를 만들어서 B, C 자동차 클래스에 상속을 하게 됩니다. 

그럼 이제 코드로 한번 살펴보겠습니다. 우선 자동차 클래스입니다. 

이런 식으로 추상 클래스로 BaseCar라는 클래스를 구현할 수 있겠죠. 이제 각 제조사마다 자동차들은 이 BaseCar라는 클래스를 상속받아 자신들의 고유한 자동차를 만들 것인데요. 이때 인터페이스로 두가지 기능을 미리 선언해주겠습니다. 첫 번째 인터페이스는 크루져 모드, 두 번째 인터페이스는 자율주행입니다. 

이렇게 두가지 인터페이스를 구현하였습니다. 인터페이스는 실제 기능은 구현하지 않고 이런 기능을 사용할 것이다라는 정의만 하고 실제 구현은 클래스에 구현하게 됩니다. 

이제 A 자동차를 구현하도록 하겠습니다. A 자동차는 BaseCar와 ICruiserable를 상속받는데요.

이런식으로 구현합니다. 이때 에러가 나는 것을 볼 수 있는데요. BaseCar에 함수와 ICruiserable의 함수를 A_Car에서 구현하지 않았기 때문에 이런 에러가 생기는 것입니다. 

이런식으로 구현을 해주면 에러가 안나게 됩니다. 이때 빨간줄이 나온 곳에 마우스를 올려놓고 Ctrl + . 을 누르게 되면 쉽게 자동으로 구현해줍니다. throw new NotImplementedException() 을 지워주고 원하는 기능을 구현하면 됩니다. 이런 방법으로 자동차 A, B, C를 구현하면 됩니다. 

여기서부터는 클래스 구조를 잘 잡아 필요한 기능을 미리 구현하고 상속 받도록 하여 중복된 개발이 되지 않으면서 클래스가 잘 구현되도록 해야합니다. 그리고 C#에서 인터페이스를 사용하면서 좋은 점은 해당 클래스를 인터페이스로 구분 할 수 있어요. 

예를 들면 ICruiserable만 상속받은 자동차들만 함수로 입력을 받고 싶을때 Type을 ICruiserable로 하면 해당 클래스에서 ICruiserable이 없는 클래스는 입력을 받을 수 없게 됩니다. 또 함수 내부에서는 Type을 비교하여 Boxing/ UnBoxing이 가능하기 때문에 마치 여러 클래스를 상속받아 사용하는 것과 같은 효과를 볼 수 있습니다. C#에서는 다중 상속이 불가능하기 때문에 인터페이스를 잘 활용한다면 큰 도움이 됩니다. 

지난 포스팅에서 Parallel을 사용하여서 병렬처리하는 방법에 대해서 이야기 했습니다. 아래 글을 참고하시면 볼 수 있습니다. 

https://math-development-geometry.tistory.com/37

불러오는 중입니다...

이번에는 병렬처리시 Thread에 안전한 ConcurrentBag을 사용하는 방법에 대해서 이야기해보겠습니다. 우선 그전에 ConcurrentBag이란 무엇인지 알아보겠습니다. ConcurrentBag은 평소 자주 사용하는 List을 병렬처리하여 추가하거나 삭제하는 경우에 데이터가 안전하게 보관되도록 만든 클래스입니다. 사용법은 아주 간단한데요. List와 똑같이 사용하면 됩니다. 

List와 같은 방법으로 선언하여 데이터를 추가하였습니다. 현재 각 공간에 1,2,3,4 가 들어있습니다. 여기서 삭제하는 것을 알아보면 List 에서는 Remove, RemoveAt, RemoveAll과 같은 함수가 있습니다. 이때 List에서는 데이터 삭제가 일어나면 각 item들의 인덱스와 링크 주소가 바뀌게 됩니다. 그렇기 때문에 한개의 item을 삭제하고 있는 중에 다른 item을 삭제하면 문제가 생기게 됩니다. 평소 병렬 프로그래밍을 하지 않았다면 전혀 문제가 되지 않았을 것입니다. 왜냐하면 한개를 삭제하고 삭제가 끝나면 다음 item을 삭제하기 때문이죠.

하지만 병렬처리를 하게 되면 한개의 item이 삭제되고 있는 중에 다른 item이 삭제될 수 있어요. 그렇기 때문에 에러가 생기게 됩니다. 이때 에러를 막기 위해서 사용하는 것이 ConcurrentBag인 것입니다. 여기에는 List에 없는 함수가 있는데요. TryPeek과 TryTake입니다. 함수명에 Try가 있기 때문에 반환값은 bool 값인데요. 해당 함수가 성공하면 true 실패하면 false를 반환하는 것을 알 수 있습니다. TryPeek 함수는 해당 ConcurrentBag에서 가장 마지막에 추가된 item이 무엇인지 out 키워드를 통해서 반환하는 함수입니다. 반환할때 해당 ConcurrentBag에서 item이 삭제되지 않습니다. 반대로 TryTake함수는 마지막에 추가된 item이 반환되면서 해당 ConcurrentBag에서 삭제가 됩니다.

그렇기 때문에 필요한 함수를 잘 선택하여 사용하는 것이 좋습니다. 

실제 List를 사용하여 Parallel를 돌리게 되면 다음과 같이 에러 메세지가 생길 수 있습니다. 하지만 ConcurrentBag을 이용하면 같은 코드지만 문제 없이 돌아갑니다.

사실 이 내부를 구현하기 위해서는 다양한 작업이 필요한데요. C# 같은 경우에 이런 번거로운 작업을 대신 해주고 있기 때문에 정말 편하게 사용할 수 있는 언어입니다. 단 내부가 어떻게 돌아가고 어떤 방식인지 알고 사용하는 것과 모르고 사용하는 것은 큰 차이가 있겠죠. 다음에 기회가 되면 내부가 어떤식으로 작동하는지에 대해서 이야기해보겠습니다.

컴퓨터가 발달되면서 다양한 기법들이 생기고 있습니다. 그 중에서 가장 중요하면서 당연한 기법이 병렬처리입니다. 대부분의 사람들은 한 순간에 한가지 일을 하는걸 힘들어 하지만, 컴퓨터는 한번에 여러가지일을 쉽게 처리할 수 있도록 설계되어 있는데요. 여러개의 cpu에 동시에 작업을 하도록 하여 처리 속도를 올리는 것입니다. 그렇다면 C#에서는 어떻게 병렬처리를 할 수 있는지 알아보겠습니다.

.Net 4.0에서 Parallel 이라는 것이 지원되었습니다. Parallel은 병렬 프로그래밍을 쉽게 할 수 있도록 만든 클래스입니다. Parallel은 System.Threading.Task에 가면 찾을 수 있습니다. Parallel에서 가장 많이 사용하는 것은 For와 ForEach입니다. 

우선 Parallel.For부터 살펴보면, 어떤 인덱스를 갖고 0번부터 100번까지 { } 안에 작업을 병렬로 처리하겠다는 뜻입니다. 기존의 for문은 index가 증가하거나 감소하면서 순서대로 하나씩 작업을 실행하지만, 위의 경우는 작업을 여러개로 나눠 동시에 실행하기 때문에 순서대로 실행되지 않습니다. 대신 더 빠른 속도로 작업을 끝낼 수 있습니다. 

ForEach를 사용하여서도 병렬처리를 할 수 있는데요. 이때는 List와 같이 여러개의 데이터가 들어가 있는 공간이 필요합니다. 예를 들어서 반 아이들의 키를 저장하고 있는 List가 있다고 할때, 이 키를 이용해서 바지 사이즈를 자동으로 측정하는 프로그램을 만든다고 가정해보죠. 이때 반 아이를 0번부터 100번까지 for문을 돌리면서 한명씩 측정할 수 있겠죠. 하지만 이보다 더 빠르게 하기 위해서는 ForEach를 이용해서 동시에 여러명을 측정하는 것이 가능해지는 것이죠. 이때 측정하는 함수는 각각 다른 메모리에서 서로의 데이터를 공유하면 안되는데요. 이 이야기는 뒤에 Invoke에 대한 설명을 보시면 이해가 될 것입니다. 

마지막으로 많이 사용하는 병렬처리 방법 중에 Invoke가 있습니다. Invoke는 특정 동작을 병렬로 실행할 수 있는데요. 

사용법은 이렇게 사용합니다. 동시에 함수 1~5를 실행한다는 함수입니다. 사용법은 정말 쉽습니다. 병렬처리를 하는데 있어서 가장 중요한 것은 메모리를 관리하는 것인데요. 만약에 각 함수가 실행되면서 같은 값에 접근하면 어떻게 될까요? 예를 들면 택시요금을 측정하는 기계를 만들었다고 할께요. 이 택시 요금 측정기는 인터넷에 연결되어 있어서 각 택시마다 요금이 어떻게 측정되는지 관리하고 기록할 수 있어요. 그런데 서버에 한 메모리에 가격이라는 변수가 있는데, 모든 택시에서 한 변수에만 접근해서 가격을 올리거나 내리거나 초기화한다면 어떻게 될까요? 아마 어떤 사람은 택시 요금이 10만원이 나오고 어떤 사람은 기본료만 내는 상황이 생길 수 있겠죠. 아마 자기 마음대로 요금이 바뀔 것 입니다. 각 택시들은 각자 독립적인 메모리를 갖고 있어야 안전하게 요금을 관리 할 수 있습니다.

이렇듯이 병렬처리를 할때는 입력과 출력을 정확하게 구분짓고 함수 내부에서 사용되는 메모리가 서로 공유되지 않도록 하는 것이 중요합니다. 물론 의도적으로 공유할 수 있지만 좋은 방법은 아니라고 생각됩니다. 

C# 에서 많이 사용하는 Dictionary에 대해서 이야기하겠습니다.

Dictionary 란 단어의 뜻은 사전이죠. 

사전에는 ㄱ,ㄴ,ㄷ,ㄹ 과 같이 키워드가 있고

해당 키워드를 기준으로 원하는 정보를 찾아요.

 

Dictionary도 이와 비슷합니다.

Dictionary에는 Key와 Value가 있고

이 Key와 Value를 통해서 데이터를 저장하고 찾기도 합니다.


예를 들어 전화번호부를 Dictionary를 이용해서 만든다고 해볼까요?

 

김민수 010-5323-4303

임시양 010-3837-3943

박만덕 010-5839-3482

이렇게 3명의 전화번호가 딕셔너리에 저장되어 있다고 가정하겠습니다.


우선 PhoneNumbers 라는 Dictionary를  선언해주겠습니다.

Key는 string으로 이름이 들어갑니다. Value는 string으로 전화번호가 들어가죠.

[ ] 사이에 Key를 입력하고 해당 Value를 넣어줍니다.

이렇게 하면 우리가 선언한 Dictionary에는 3명의 전화번호가 저장되어있고 이 전화번호는 이름을 통해서 찾을 수 있습니다.

데이터를 추가하는 또 다른 방법으로 Add 함수를 이용할 수 있습니다.


이번에 우리가 입력한 데이터를 핸들링 하는 방법을 알아보겠습니다.

해당 딕셔너리에 우리가 찾고 싶은 Key가 있는지 확인하기 위해서 사용하는 함수는 ContainsKey입니다.

ContainsKey라는 함수를 이용해서 찾고 싶은 Key를 입력하면 Dictionary에서 Key가 존재하는지 확인하고 그 결과를 반환합니다. 만약 찾았다면 true를 찾지 못했다면 false를 반환하겠죠.

이외에도 ContainsValue라는 함수도 있습니다. 사용방법은 ContainsKey와 같고 반환 값도 같습니다. 만약에 ContainsKey를 이용해 Key가 존재하는지 확인했다면 []에 해당 Key를 입력하여 데이터를 가져옵니다. Key로 데이터를 가져올때는 get; set;이므로 데이터를 가져올 수 있고, 위에서 사용한 것처럼 데이터를 설정하는 것도 가능합니다.


두번째로 알아볼 내용은 정렬인데요. Dictionary는 Key나 Value를 기준으로 정렬하는 것이 가능합니다.

전화번호부를 만들때 아무렇게나 입력된 데이터를 정렬하는데 아주 좋은 기능이죠.

OrderBy라는 함수를 이용합니다.

위와 같이 김민수, 임시양, 박만덕이 Key로 저장되어 있을때 가나다 순으로 정렬하면 김민수, 박만덕, 임시양 순서가 되겠죠. 이럴때 OrderBy 함수를 사용하고 item의 Key값으로 정렬을 해줍니다. (만약 Value로 정렬하고 싶다면 Value를 써주면 됩니다.)

이렇게 되면 결과는 다음과 같습니다.

내림차순으로 정렬하고 싶다면 OrderBy 대신에 OrderByDescending를 사용하면 됩니다. 이밖에 GroupBy 함수도 존재해서 특정 조건을 이용해 원하는 Item을 가져올 수 있습니다.


Dictionary는 List나 배열과 같이 인덱스를 이용하여 데이터를 찾을 수 없습니다. 따라서 일반적인 반복문을 이용하는 것은 불가능한데요. 이럴때 사용하는 것이 foreach입니다.

foreach를 이용하면 다음과 같이 사용가능하고요.

Dictionary에 데이터가 정렬된 순서대로 item를 반환합니다.

따라서 Key에는 이름이 저장되고 Value에는 전화번호가 저장되겠죠.

프로그래밍을 하다보면 예상하지 못한 문제를 만나게 됩니다. 이런 문제를 적절하게 해결하지 않는다면 프로그램이 중간에 멈추거나 작동하지 않는 결과가 생기죠. 


이런 문제를 해결하기 위한 도구로 예외처리라는 것이 있습니다. C#에서는 다양한 exception을 제공하고 있습니다.

흔히 사용될 수 있는 파일을 읽을때 발생하는 예외, 인덱스를 벗어나는 경우 발생하는 예외, 다른 서버에 접속할때 발생하는 예외 등 수많은 예외를 C#에서 미리 만들어 놓고 사용할 수 있게 해줍니다.

예외처리를 사용하기 위해서는 try catch라는 문법을 사용하고 finally를 사용해서 예외처리와 상관없이 진행되는 코드를 넣을 수 있어요.

try
{
    예외가 발생할 수 있는 코드
}
catch (execption e)
{
   예외가 발생했을때 대처할 코드
}
finally
{
   예외처리와 상관없이 try구문을 벗어날때 실행되는 코드
}

이렇게 코드가 구성되어 있습니다.
exception을 빼고 c#에서 제공하는 다양한 exception을 써도 상관없습니다.
또 여러가지 catch문을 써서 다양한 예외처리를 한번에 할 수 있어요.
그리고 throw라는 키워드를 사용해서 자신이 원하는 exception 도 만들 수 있어요.

try
{}
catch(execption e)
{
   throw new Execption("  여기서 에러 ");
}





+ Recent posts