지뢰찾기, Microsoft Windows에 포함된 게임 중 하나로 Windows7 까지 운영체제를 설치하면 Windows에 기본으로 포함되어있던 기본게임으로 Windows를 사용해본 사람이라면 게임을 플레이하는 제대로된 방법은 모를지라도 지뢰찾기라는 게임이 있다는건 모를수가 없는 인지도있는 게임입니다.
지뢰찾기 게임 이미지
이 게임은 간단한 게임방식, 쉬운 접근성과 재미 등 많은 사람들이 게임을 플레이하며, Windows 기본 게임임에도 마니아 층이 두터운 게임이 되었으나 Windows8부터 지뢰찾기는 Windows에 포함되지 않아 플레이하기 위해 별도의 다운로드가 필요한 게임이 되어버렸습니다.
개인적으로 저 또한 이 게임을 좋아하는 마니아로, 이제 쉽게 접할 수 없게된 지뢰찾기에 대해 매우 아쉬워했습니다. 컴퓨터에 당연하듯이 있었던 지뢰찾기를 이제는 가끔 생각나서 한번할려고하면 인터넷에서 지뢰찾기를 찾아야 했으니까요. 그렇게 아쉬워하며 지뢰찾기를 찾던 어느날... '내가 만들수 있지 않나......?' 라는 생각이 갑자기 들면서 저는... 이 게임을 만들게 되었습니다.
이 게임은 C#으로 만든 프로그램이며, 원본 게임인 지뢰찾기가 간단한 게임방식을 가지고있는만큼 이를 제작하는데 큰 기술을 요구하지는 않았으며, 제가 지뢰찾기를 좋아해서 그런지는 몰라도 저는 이 게임을 만드는데도 큰 재미를 느끼며 만들었습니다.
이제 저는 지뢰찾기를 좋아하다 못해 '지뢰찾기를 만든 사람'으로 어디가서 지뢰찾기 얘기가 나오면 '지뢰찾기 내가 만들었다'라며 어그로를 끌 수 있는 능력이 생기게 되었습니다.
프로그래머로서의 지뢰찾기
제가 주변에 지뢰찾기를 만들었다라고 얘기를 하면 보통 '할일이 없냐?', '뭐 그걸 만들어서 까지하냐?', '다운로드 받으면 되는거 아니냐?'라는 좋지 않은 얘기를 듣기도 하는데요. 하지만 지뢰찾기는 그렇게 무시할만한 게임이 아닙니다.
지뢰찾기는 높은 인지도와 간단한 게임방식, 쉬운 접근성과 재미로 두터운 마니아층을 보유하고 있으며, 그를 증명하듯 현재까지도 여러 파생게임과 아류게임들도 많으며, 프로그램적으로 지뢰찾기는 학교 및 학원 등의 교육기관, 프로그램 실력을 테스트하는 코딩테스트에서도 등장하며 프로그램적으로 아주 유익한 게임입니다.
코딩테스트 사이트 백준의 문제 검색결과지뢰 찾기 나무위키의 일부
그래서 프로그래머 입장에서 지뢰찾기를 만드는 것은 절대 손해가 아닌 유익한 것으로 한번쯤 만들어 보면 좋을 것 같다는 생각을 합니다. 특히 재귀함수를 공부하시거나 알기는 아는데 '재귀함수를 어느 상황에 써야하는지 모르겠다.'라고 하시는 분들에게 적극 추천 드리겠습니다.
지뢰찾기의 단점
지뢰찾기는 간단한 게임방식, 쉬운 접근성과 재미로 두터운 마니아 층까지 가지고 있으며, 여러 파생게임과 아류게임, 심지어 프로그램적 교육 가치도 있는 한마디로 갓겜으로 이 완벽한 게임에도 치명적인 단 하나의 단점있으니 그것은...
설정가능한 데이터를 미리 삽입해두고 사용자가 그 중 하나를 선택할 수 있게 도와주는 역할을 하는 ComboBox, 일반적으로는 아래 이미지와 같이 그냥 편집창을 열어서 한줄한줄 데이터를 입력하실꺼라 생각합니다.
수동 ComboBox 데이터 추가
하지만 프로그램을 하다보면 이 Combobox와 열거형(Enum)을 같이 사용해야 할 때가 종종 있습니다. 예를 들면, 특정 모드나 설정, 세팅 변수들을 열거형으로 지정해 놓고 ComboBox에 추가한 다음 사용자한테 어떻게 설정할지 또는 어떻게 동작할지를 고르게 하는 경우가 있을텐데요. 이 경우 특정 모드 또는 설정은 열거형(Enum)으로 만들어 두시고 사용하시는 분들이 많을겁니다. 이 경우 위 이미지 처럼 수동으로 ComboBox에 값을 타이핑하지 않고 프로그램에서 추가하는 방식을 많이 선호하실거라 생각합니다.
저는 보통 Button을 사용자가 눌렀을 때 동작하는 이벤트를 사용하면 보통 'Click'이벤트를 많이 사용합니다. 그 이유는 Button을 더블클릭하는 단순한 동작으로 'Click'이벤트가 생기기 때문인데요. 아마 다른분들도 많이 사용하실꺼라 생각합니다.
하지만 Click이벤트는 기본적으로 마우스로 클릭하거나, 버튼이 선택된 상태에서 Enter또는 Space를 누르게 되면 Click이벤트가 동작이 되는데 즉, 마우스랑 키보드로 둘 다 작동시킬 수 있다는 얘기입니다. 이 글을 보면 '마우스랑 키보드 둘 다 조작이되니까 편한것 아니냐?'라고 생각이 들기도 하는데, 바로 이 부분 때문에 문제가 됐습니다.
이미 진행했던 프로그램을 사용하던 고객님이 버튼이 키보드로 동작이되서 오작동하는 경우가 있다고, 키보드로는 동작하지 않게 수정해달라고 한것인데요........ 이 것이 참 난감했습니다.
어려운 프로그램은 아니지만, 해당 프로그램은 버튼이 백개 정도가 쓰이며, 한번 수정해 준다고 끝나는 것이 아니라 앞으로 유지보수할 때 버튼이 추가된다면 그 때마다 이 내용을 기억하면서 처리를 해줘야 한다는 것인데..... 음....
일단 이 문제를 잘 해결하기 위한 조건을 몇 가지 생각해봤습니다.
Click이벤트를 써야한다. - MouseClick를 사용해도 되지만, Visual Studio에서 손쉽게 Click이벤트를 생성해 사용할 수 있기 때문에 나중에 Click이벤트를 만들어 사용할 때도 이상이 없도록 Click을 사용하는 방안으로 해야함(편의성)
100개나 되는 Click이벤트를 일일이 수정하지 않아야한다.- 이는 완전 단순반복 작업이며, 실수할 소지도 많고 시간도 많이 들며, 나중에 버튼을 추가해서 사용할 시, 문제가 될 수 있음(효울성)
시간이 지나 나중에 버튼을 추가할 때, 이 내용을 기억하지 않더라도 키보드로는 동작하면 안되게 자동화. - 한번 하고 끝나는게 아니라 계속 똑같은 설정을 해줘야하는데, 이를 기억력에 의존하지 않기위함(자동화)
위 조건을 최대한 지키며 프로그램을 하고 싶어 자료를 찾아보며 프로그램을 하려다보니 생각보다 이런일이 없으신건지 제대로된 자료를 찾지 못해서 제가 써봅니다.
프로그램 적용
위 문제를 해결하기 위한 제 방법은 아래와 같습니다.
public void AllRemoveEvent(Control parent)
{
foreach (Control c in parent.Controls)
{
if (c as Button != null)
{
string methodName = string.Format("{0}_Click", c.Name);
RemoveEventHandlers(parent, c, "Click", methodName);
}
else if (c.HasChildren)
{
AllRemoveEvent(c); // 재귀호출
}
}
}
public bool RemoveEventHandlers<T>(T container, object target, string eventName, string eventmethodName)
{
if (target == null)
return false;
Type targetType = target.GetType();
EventInfo eventInfo = targetType.GetEvent(eventName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); // 이벤트 찾기
if (eventInfo == null)
return false;
Type containerType = typeof(T);
MethodInfo methodInfo = containerType.GetMethod(eventmethodName, BindingFlags.NonPublic | BindingFlags.Instance); // 이벤트 메서드 찾기
if (methodInfo == null)
return false;
Delegate eventDelegate = Delegate.CreateDelegate(typeof(EventHandler), container, methodInfo); // 이벤트 메서드 -> 델리게이트
if (eventDelegate == null)
return false;
eventInfo.RemoveEventHandler(target, eventDelegate); // 기존 이벤트 삭제
eventInfo.AddEventHandler(target, new EventHandler((sender, args) => // 새 이벤트 등록
{
if (args is MouseEventArgs) // 마우스 클릭확인
{
methodInfo.Invoke(container, new object[] { sender, args });
}
}));
return true;
}
위 소스코드는 리플렉션(Reflection)을 사용하여 기존 등록된 Event Method를 찾아, 해당 Button에 Click이벤트에서 제거 하고 새로운 이벤트를 생성하여 마우스로 작동시킨 경우에만 기존 Event Method를 작동 시킨 소스입니다.
이 함수를 사용하는 방법은 어떤 버튼이 마우스로만 동작을 원하는 Form생성자에 아래와 같이 사용해주시면 됩니다.
AllRemoveEvent(this);
설명
위 소스코드는 AllRemoveEvent메서드와 RemoveEventHandlers메서드를 구현했으며 각각의 역할은 아래와 같습니다.
AllRemoveEvent : parent에 있는 하위 Control을 포함한 Control중 Button을 찾아 RemoveEventHandlers()을 호출한다.
RemoveEventHandlers : container에 정의된 Event Method를 찾아 target에 등록된 이벤트를 제거하고 마우스로 작동했을 때 기존 Event Method를 실행한다.
AllRemoveEvent 는 재귀호출을 통해 하위컨트롤까지 Button을 찾아 RemoveEventHandlers에 Button객체와 제거할 이벤트명, Event Method이름, Event메소드가 정의된 container를 전달하며, RemoveEventHandlers에서는 container에 정의된 EventMethod를 찾아 Click이벤트에서 제거하고 새로운 이벤트를 만들어 마우스로 눌렀을때만 기존 메서드가 동작하게 만듭니다.
위 소스코드는 리플렉션에 대한 지식이 있으시면 쉽게 이해가 가능하실텐데, 리플렉션 (Reflection) 에 대해 잘 모르시면 그 부분을 따로 알아보고 오시면 좋습니다. 기회가 되면 리플렉션에 대해서도 포스팅해보겠습니다.
위 소스코드의 단점
Click이벤트로 등록된 Event Method의 이름이 '[button이름]_Click' 이여야 정상작동을 합니다. 버튼명이 변경 되었거나 버튼명과 이벤트명이 다르면 사용불가.
Event Method가 container에 정의되어있지 않으면 사용이 불가능.
체감이 크게 갈지는 잘 모르겠으나, 일반적으로 리플렉션은 프로그램의 성능을 저하시키기는 걸로 알려져 있는데, 이 소스코드도 마찬가지 입니다. (리플렉션의 성능을 향상시키는 방법도 존재하기는 하는데 이것도 나중에 한번 포스팅하겠습니다.)
그래서 이 글의 요약 ......
위 소스는, 위에 설명된 단점때문에 상황에 따라 이 글을 보는 모든 사람이 사용하긴힘들겠지만 '이런 방법으로 하면된다!' 또는 '이런 방법도 있다!' 라는걸 공유하기 위해 글을 썻습니다. 즉 소스가 개판으로 보일시 양해바라고
영어로는 hexadecimal(헥사데시멀), 보통은 줄여서 hex(헥사 또는 헥스)라고 하며, 컴퓨터 분야에서는 이진법과 마찬가지로 가장 많이 사용하는 표현 방식인데요. 이전에 포스팅했던 이진법은 0과 1을 표현하는 비트로 이루어진 표현 방식이였는데 이번 16진법은 숫자 10개(0-9)와 영어 알파벳 6개(a-f), 총 16개의 문자로 이루어져 있으며, 이 문자들은 각각 0에서 15까지의 값을 표현하고 있는데 0-9는 그냥 0-9를 표현 하고, 알파벳은 각자...
a = 10, b = 11, c = 12, d = 13, e = 14, f = 15의 값 표현하고 있습니다. (대문자도 의미는 같습니다)
여기서 한가지 먼저 집고 넘어가자면,16진법은 16개의 문자로 16을 넘지 않는 0-15까지 수를 표현하는 문자, 10진법은 10개의 문자로 10을 넘지 않는 0-9까지 수를 표현하는 문자, 2진법도 2개의 문자로 2를 넘지않는 0-1의 문자로 수를 표현하고 있습니다. 그런데 여기서 혹시, 10진법이 0-9까지를 표현하는 수로 이루어져 있다고 말씀드렸는데...
'10진수는 100도 있고 1560도 있고 표현할 수 있는 수가 많은데 왜 9까지 밖에 표현을 못한다고 하는거야?' 라고 생각하신다면, 위에 예시인 100, 1560은 모두 0-9사이의 문자로 이루어져 있으며, 당장 10을 표현할려고 해도 1과 0두개로 표시해야 하는 겁니다.
다시 16진법으로 돌아와서, 16진법으로 표현한 16진수는 보통 '0x'라는 문자를 앞에 붙여 사용하며, 내가 지금쓴 글이 16진수임을 알려주는 식별자와 같습니다. 예를 들어 몇가지 써보면... 0x00, 0x2f, 0x015d, 0xb1a4 등 '0x'를 앞에 붙이며 그 다음 특징으로는 보통 '0x'뒤에 문자를 2개 씩 붙여서 쓰는데, 제가 쓴 예시를 0x를 빼고보면 00, 2f, 015d, b1a4로 두 글자씩 쓴다는 겁니다. 이는 2진법과 관련이 있으며, 이 내용은 아래에서 따로 설명하겠습니다.
16진법의 10진수변환
16진법은 0에서 15까지의 수를 표현하는 0 - 9 및 a - f까지의 문자로 이루어져 있으며, 만약 10을 표현할려면 0x0a, 13을 표현 할려면 0x0d, 15를 표현할려면 0x0f라고 써야 하며, 만약 '15를 넘어가는 16를 표현하고 싶다'라고 한다면 0x10이라고 표현을 하면 됩니다. 이것은 자리 수를 넘기는 행위이며, 10진수의 숫자 구(9)보다 하나 큰 수인 십을... 10의 자리를 1올리고 1의 자리를 0이라고 바꿔 '10'이라고 하는 것과 같습니다. 이를 정리하면...
16진법 자리수
위 이미지와 같이 정리할 수 있으며, 오른쪽 끝에 칸의 자리수를 1로 시작해서 왼쪽으로 한칸 이동할 때 마다 16배씩 증가하며, 각 칸에는 0~f(15)의 값이 들어갈 수 있습니다. 이 때 위에 예시로 든 0x0f, 0x10의 값을 수식을 써서 표현하면...
0x0f는 16의 자리가 0, 1의 자리는 f(15)이므로 [16 * 0] + [1 * f(15)]를 계산해서 15,
0x10은 16의 자리가 1, 1의 자리는 0으로 [16 * 1] + [1 * 0]을 계산해서 16 으로 계산됩니다.
그렇다면, 16진법으로 0xd7이라고하면 어떤 값이 나올까요?
예시
그냥 똑같이 하시면 됩니다. 16의 자리가 d, 1의 자리가 7이므로 [16 * d(13)] + [1 * 7]을 계산해서 215가 나옵니다.
16진법과 2진법 변환
이 글을 읽기전 이진법을 모르시는 분들은 이진법 포스팅을 보고오시기 바랍니다. 이진법 포스팅
위 글에서 잠깐... 16진법은 0x00, 0xb1a4 처럼 문자를 두개씩 쓰는 것이 2진법과 관련이 있다고 했는데요. 이는 사실 컴퓨터의 바이트랑 연관되어 있습니다.
바이트에 관해 잠깐 설명드리면 바이트(byte)는 8bit의 단위이며, 1byte는 = 8bit인데요. 컴퓨터의 가장 작은 최소단위는 비트이지만, 일반적으로 용량같은 단위를 표시할 때는 킬로바이트(KB), 메가바이트(MB), 기가바이트(GB)등 byte라는 단위를 많이 사용합니다.
이렇게 컴퓨터는 바이트(byte)라는 단위를 많이 사용하는데, 이는 16진법도 마찬가지 입니다. 16진수의 0~f까지의 값은 이진수 보면 4bit로 표현이 가능하고 이 값을 두개 사용하는 0x00, 0xd7, 0xff와 같은 16진수들은 8bit 즉 한개의 바이트를 나타내는 것이죠. 그렇다면 16진수를 2진수로 바꾸는 방법도 알아봅시다.
16진수의 0~f는 십진수로 0~15를 표현한다고 알려드렸는데, 4개의 bit도 0~15를 표현할 수 있습니다. 즉 1대 1 매칭을 하면 0~f를 이진수로 바꿀수 있게 되는 것이며, 0xca같이 16진수가 두개가 들어가는 건 똑같이 c랑 a을 각각 매칭시켜주면 끝나는 것인데요.
16진수
2진수
16진수
2진수
0
0000
8
1000
1
0001
9
1001
2
0010
a
1010
3
0011
b
1011
4
0100
c
1100
5
0101
d
1101
6
0110
e
1110
7
0111
f
1111
위 표가 0~15를 표현하는 16진수와 2진수의 표 인데요. 이표를 기준으로 0xca을 이진수로 변환해보면....
16진수 c는 2진수로 1100, 16진수 a은 2진수로 1010 으로 16진수 0xca은 2진수로 1100 1010로 쓰면 됩니다.
0~f까지의 값을 2진수로 매칭만 시켜주면 참 쉽죠?
그럼 16진수 0xca와 2진수 1100 1010이 정말 같은 값인지 확인하기 위해 둘 다 10진수로 변환해서 같은지 확인해봅시다.
16진법 0xca
먼저 16진수 0xca부터 10진수로 변환하면 C는 12고 a는 10이죠 그럼.... [16 * c(12)] + [1 * a(10)]으로 계산해서 202가 나오고,
2진수 1100 1010
이제 이진수인 1100 1010을 10진수로 변환해보면, 의미 없는 0자리는 빼고 [128] + [64] +[8] + [2] 로 202가 나옵니다. 그러므로, 0xca와 이진수 1100 1010은 서로 202로 같으며, 이는 0xca = 1100 1010 =202로 표현할 수 있습니다.
글을 마치며
사실 이번글은... 이진법 포스팅을 할 때 16진법도 같이 작성할려고 했으나, 글이 너무 길어져 가독성이 떨어질 것을 우려해이진법과 16진법으로 따로 글을 작성했는데요. 그래서 이번글 전체를 이해할려면 이진법에 대한 지식이 있어야한다는 제약이 생겼습니다....
짧게 압축하면 한 포스팅에도 작성할 수 있을 것 같은데... 일단 죄송합니다. 가능하면 보고오시길 추천드립니다.
binary digit 줄여서 비트(bit). 0과 1의 값만 가질 수 있는 컴퓨터의 가장 작은 단위로, '컴퓨터는 0과 1로만 이뤄져 있다' 라는 얘기를 아마 한번쯤은 들어보신적이 있을텐데요. 이게 바로 비트를 얘기하는 것 입니다.
흔히 컴퓨터나 핸드폰 용량 등을 얘기할 때 쓰이는 바이트(byte), 킬로바이트(KB), 메가바이트(MB), 기가바이트(GB), 테라바이트(TB) 등도 모두 비트로 이루어져 있는 단위들이며, 컴퓨터는 이 가장작은 단위인 비트(bit)를 무수히 많이 사용하여 우리가 필요한 많은 일을 처리합니다.
비트(bit)의 숫자표현 - 이진법알아보기
1개의 비트는 두가지의 숫자를 표현할 수 있습니다. 그것은 처음부터 계속 얘기한 0과 1이죠. 그렇다면 2이상의 숫자는 어떻게 표현할까요? 사람의 손으로 생각해보면 생각보다 간단합니다. 사람의 손이 주먹을 쥔 상태가 0이라고 한다면 사람의 손은 0에서 5까지 6개의 숫자(0,1,2,3,4,5)를 표현할 수 있습니다. 그렇다면 6을 세는 방법은 뭘까요? 그건 한손을 더 쓰는 겁니다.
비트도 마찬가지로 한개의 비트로 2를 표현하지못해 비트를 한개 더 사용하며, 좀 더 큰 수를 표현할려고할 때마다 더 많은 비트를 사용합니다. 이를 수식으로 표현하면 비트의 수가 n이라고할 때 2의 n승으로 표현할 수 있으며, 비트가 8개라고할 때 2의 8승인 256(0~255)개의 수를 표현할 수 있습니다. 이 때 예시로든 8개의 비트는 byte라는 단위로 부르며 앞으로의 예시는 byte의 단위인 8비트로 설명하겠습니다.
위 이미지는 0과 1이 들어갈 수 있는 비트를 8개를 나열한 것이며, 1개의 byte를 나타낸 것 입니다. 이 때 각 비트에는 자리수라는 개념이 들어가는데, 이 자리수는 여러분들이 알고 있는 일반적인 숫자에... 1의 자리, 10의 자리, 100의 자리, 1000의 자리 등에 쓰이는 자리수랑 같으며, 백의 자리가 1 십의 자리가 5, 일의 자리가 2라고 할 때, [100 * 1] + [10 * 5] + [1 * 2]로 계산해서 152가 되듯이 이진법에서도 동일하게 [자리수 * 비트의 상태]을 모두 더하면 값이 나오는데, 이 때 이진법의 자리수는, 위 이미지와 같이 오른쪽 끝을 1로 시작해서 왼쪽으로 한칸씩 이동할 때마다 2배씩 증가합니다.
예시로 0과 1이 반복되는 위 이미지와 같은 byte가 있을 때 이 byte의 숫자값은.... 각 [자리수 * 비트의 상태]를 모두 더한 아래의 식이 나오게 되며, 그 값은....
위 계산대로 85입니다. 생각보단 어렵지 않죠? 그리고 위 예시인 비트의 상태 01010101을 이진수(2진수)라고 부르고 우리가 흔히 쓰는 0,1,2,3,4,5,6,7,8,9,10,11,12,13.......이런 수를 십진수(10진수)라고 합니다.
십진수를 이진수로 바꾸기
위에 내용은 컴퓨터가 비트로 수를 표현하는 이진법을 알아봤는데, 그러면 우리가 아는 수... 즉, 십진수를 이진수로 바꾸는건 어떻게 하는걸까요? 제일 간단한 방법은, 이 방법이라고 생각되는데, 바꾸고 싶은 수 X를 2로 나누고 몫과 나머지를 확인하여 몫이 0이될 때 까지 반복하는 것입니다.
예를 들어, 십진수로 85인.... 위 이미지에 나와 있는 byte를 다시 이진수로 바꿔보는 예를 계산해보면...
2 / 85
2 / 42...1
2 / 21...0
2 /10...1
2 / 5...0
2 / 2...1
2 / 1...0
0...1
이렇게 되며, 나머지 값을 아래에서 부터 위로 쭉 적으면..... [101 0101]이 나오는데 이게 85를 이진수로변환한 값 입니다. 그런데 위 예시로 보여드렸던 이미지의 [0101 0101]과 비교해보면 방금 계산한 값은 7자리로 제일왼쪽 0이 빠지게 계산된것이 보이실텐데..... 이진수는 제일 왼쪽의 0은 언제나 생략이 가능하기 때문에 [0101 0101]은 [101 0101]과 같은 값 입니다.
글을 마치며
이번 포스팅은 컴퓨터가 비트(bit)로 수를 표현하는 이진법에 대해 알려드렸으며, 이진수를 우리가 쓰는 십진수로, 십진수를 이진수로 변환하는 방법을 최대한 비전공자 입장에서 알기 쉽게 글을 작성하려고 했는데, 그 의도가 잘 반영이 되었나는 잘 모르겠으나... 십진수를 이진수로 변환하는 방법에 대해서는 '제가 아는 제일 간단한 방법'을 알려드렸는데...
'무슨소리인지 모르겠다.', '이게 뭐가 쉽냐', '계산하기 너무 복잡하다' 등...
이 글로 처음 이진법을 접하시는 분들 중에 이렇게 생각하시는 분들이 많이 있을거라 생각이되며.... '제가 아는 제일 간단한 방법'..... 어려워서 죄송합니다...
C# 프로그램을 지금까지하면서 주구장창 사용해왔던 Dispose()...... 하지만 정확히 어떻게 동작하는지는 모르고 객체에 Dispose() 함수가 들어가 있으면 메모리관리를 위해 거의 무조건 Dispose()를 해왔었는데요.
Dispose()가 정확히는 몰라도 메모리를 해제해준다는건 알고 있었으니까 메모리의 누수나 가비지컬렉터가 조금이라도 덜작동하게 만들기 위해 그냥 일단 사용을 해왔고 실제로 이렇게 사용하고 나서는 특별히 메모리 관련된 문제가 나오지 않았는데요.
그러나 최근 만들었던 프로그램에 메모리문제인지 뭔지 원인을 알수 없는, 프로그램이 강제종료되는 상황이 생겨 골치아픈 나날을 보내고 있는데...... 메모리 문제인지 무슨 문제인지는 잘 모르겠으나 시간이 좀 있을 때, Dispose에 대해 알아보는 것이 좋을 것 같아 이번 포스팅을 남깁니다.
Dispose(). 마이크로소프트 문서에서는 아래와 같이 정의 하고 있습니다.
Microsoft 문서 이미지 출처 : https://learn.microsoft.com/ko-kr/dotnet/api/system.idisposable?view=net-6.0
내용은 간단하게 써져 있는데 '관리되지 않은 리소스 해제'를 위한 메커니즘을 제공합니다. 라고 나와 있습니다.
'관리되지 않은 리소스 해제' 즉 관리되는 리소스는 대상이 아니고 관리되지 않은 리소스를 대상으로 리소스를 해제한다는 얘기인데...... '관리되는 리소스'와 '관리되지 않은 리소스'는 무슨 차이가 있고 뭐가 다른 것일까요?
'관리되는 리소스'란? CLR(Common Language Runtime)의 가비지 컬렉터에 의해 관리되는 리소스로 이 리소스는 가비지 컬렉터가 알아서 리소스를 해제시켜주지만, '관리되지 않은 리소스'는 가비지 컬렉터가 리소스를 해제시켜주지 않으며 해당 리소스의 사용을 완료할 때 명시적으로 해제 해야 한다고 합니다.
즉, '관리되는 리소스'는 가비지 컬렉터가 알아서 리소스를 해제시켜 주고 '관리되지 않은 리소스'는 개발자가 Dispose()를 수행하여 알아서 리소스를 해제하지 않으면 메모리 누수가 발생하는 것인데, 이는 프로그램의 성능이 점차 떨어지고, 최악의 경우에는 프로그램이 비정상 종료되는 문제가 발생할 수 있습니다.
또한 모든 경우는 아니지만 일반적으로는 Dispose()는 '관리되지 않은 리소스'만 즉시 해제할 뿐, '관리되는 리소스'는 즉시 해제하지 않으며, 후에 가비지 컬렉터가 돌아가야 리소스가 해제되는 시스템으로 어떤 객체를 Dispose() 하였다고 하더라도 그 객체의 '관리되는 리소스'는 가비지 컬렉터가 돌아갈때 까지 남아있다는 겁니다.
간단한 프로그램이라면 모를까, '관리되지 않은 리소스'를 Dispose()를 해야하는게 사실상 선택이 아닌 필수사항인 것인데 그렇다면 '관리되지 않은 리소스'가 무엇이 있는지 한번 파악을 해보면...
파일 핸들, 네트워크 핸들, 윈도우, 데이터 베이스의 연결, 네트워크 소켓, 그래픽 핸들, System.Runtimer.InteropServices.Marshal.AllocHGlobal메서드 등이 있으며, 이는 반드시 Dispose()또는 다른 방식의 명시적인 리소스 해제를 하셔야 합니다.