C#으로 무턱대고 코드를 작성하다 보면, 코드의 전체적인 로직 상으로는 전혀 문제가 없어보이는데 이상 동작을 일으키는 경우가 있다.

예를 들면, 값이 엉뚱한 값으로 변해 있다거나(이런 경우는 차라리 짐작하기 쉽다), 프로그램이 이유없이 느리거나 락(lock)에 걸린 것 같은 현상을 간혹 보인다거나... 하는 식으로 말이다.

 

다음의 예제를 살펴보자.

 

[예제 코드]

SortedList<DateTime, string> m_alarmList; // 목록 선언

...

 

// 목적 클래스 인스턴스 생성 

targetClass cls = new targetClass(m_alarmList);

cls.SomeJob();

...

 

// 목록 조회

lock (m_alarmList)

m_alarmList = getOtherFunc(); 

...

 

// 목적 클래스의 목록 업데이트 

targetClass.AlarmList = m_alarmList;

...

 

[targetClass.cs 코드]

...

private SortedList<DateTime, string> m_list; // 로컬 목록 변수 선언

...

 

// 공용 목록 속성 정의 

public SortedList<DateTime, string> AlarmList

{

get { return m_list; }

set {

lock (m_list)

{

m_list = value; 

}

 

// 생성자 

public targetClass(SortedList<DateTime, string> list)

{

m_list = list; 

}

 

// 로컬 목록 변수 조작 작업

public void SomeJob()

{

... 

m_list.Add(DateTime.Now, "테스트 작업1");

...

 

if (m_list.ContainsKey(DateTime.Now)) m_list.RemoveAt(0); 

... 

... 

 

위 코드를 보면, 예제 코드에서 targetClass 클래스의 생성자로 SortedList 목록 개체를 만들어 넘기는데, targetClass에서는 받은 목록을 그대로 로컬 변수에 할당하여 사용한다. 이후, 예제 코드에서는 이 목록을 업데이트하는 등 여러가지 작업을 하고, 나중에 targetClass의 목록을 업데이트하기 위해 공용 속성 할당 작업을 또 한다.

 

문제가 무엇인지 짐작할 수 있겠는가?

 

그렇다. 여기서 SortedList<DateTime, string> 목록은 값이 아니라 참조 형식이라는 것을 깜박한 "낭패 코딩"이 문제였다.

생성자로 넘기고 SomeJob() 함수를 호출하는 순간 SortedList 목록은 targetClass에서 이리저리 조작된다. 그런데 문제는 이 목록이 예제 코드에서도 똑같이 조작되고 있는 것이다. "참조 형식"이기 때문이다. 값을 할당하여 targetClass 로컬에서만 사용되고 있는 것이 아니라 targetClass로 이 목록의 포인터가 넘어간 것이라는 점을 잊은 것이다.

 

참조 형식임을 제대로 인식하고 코딩을 했으면 위 targetClass.cs 부분의 코드는 다음과 같이 바뀌었을 것이다.

 

...

private SortedList<DateTime, string> m_list; // 로컬 목록 변수 선언

...

 

// 공용 목록 속성 정의 

public SortedList<DateTime, string> AlarmList

{

get { return m_list; }

set {

lock (m_list)

{

m_list = new SortedList<DateTime, string>(value); 

}

 

// 생성자 

public targetClass(SortedList<DateTime, string> list)

{

m_list = new SortedList<DateTime, string>(list); 

}

 

// 로컬 목록 변수 조작 작업

public void SomeJob()

{

... 

m_list.Add(DateTime.Now, "테스트 작업1");

...

 

if (m_list.ContainsKey(DateTime.Now)) m_list.RemoveAt(0); 

... 

... 

 

이렇게 특별한 클래스를 사용한 작업은 그나마 "클래스는 참조 형식"이라는 것을 이미 염두에 두고 있을 수 있어 문제를 빨리 찾을 수 있다.

 

그러나 가장 많이 실수하는 것은 바로 string, 즉 문자열 처리다.

string 역시 참조 형식이다. 대부분 문자열을 그냥 값 형식처럼 사용하게 되는데, 위 예제처럼 생성자 혹은 함수 인자로 넘긴 후 로컬 변수로 받아서 조작하게 되면 문제가 생길 확률이 높아진다. 조작하지 않고 그냥 읽기 전용으로만 사용한다거나, 조작을 하기 위해 의도적으로 작성한 코드라면 문제가 없지만 말이다!!

(그러나, string 클래스에서 제공하는 대부분의 함수(Replace, ToString, SubString, ...)들은 내부적으로 항상 새 인스턴스를 생성하도록 되어 있어 이 문제가 발생할 여지가 거의 없으므로 걱정없이 사용해도 무방하다. string을 char[]로 캐스팅해서 직접 내용을 수정한다거나 뭐 그런 식으로 사용하지만 않는다면!!)

 

그러므로, string 형식인 경우에도 인자로 넘겨준 후 로컬 변수에 할당해서 사용하도록 코드를 작성한다면 가능한 한 다음과 같이 string.Copy()함수를 쓰는 습관을 들이자!!

 

...

private string m_string;

...

 

public string TestString

{

get { return m_string; }

set {

m_string = string.Copy(value);

}

... 

 




Posted by 떼르미
,


자바스크립트를 허용해주세요!
Please Enable JavaScript![ Enable JavaScript ]