C# & VB.NET2009. 1. 19. 00:20

지난 번 Comb Sort를 소개했던 포스팅에서 말씀 드렸듯이, Bubble Sort, Comb Sort, 그리고 닷넷의 기본 소팅 알고리즘인 Quick Sort의 성능을 비교해보았습니다.

테스트 소스는 지난 번 소스에 Quick Sort를 호출하는 부분을 추가한 것 뿐입니다. 그리고 Quick Sort도 따로 구현한 것이 아니라 닷넷의 System.Array.Sort()를 호출하는 방식으로 했습니다.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SortingSample
{
   
class Program
   
{
       
static void Main(string[] args)
       
{
           
int dataCount = Convert.ToInt32 (args[0]);
           
string[] BubbleUnsorted = new string[dataCount];
           
string[] CombUnsorted = new string[dataCount];
           
string[] QuickUnsorted = new string[dataCount];
           
string[] BubbleSorted = new string[dataCount];
           
string[] CombSorted = new string[dataCount];
           
string[] QuickSorted = new string[dataCount];
           
Random RandObj = new Random();
           
for (int idx = 0; idx < dataCount; idx++)
           
{
               
BubbleUnsorted[idx] = RandObj.Next().ToString();
               
CombUnsorted[idx] = RandObj.Next().ToString();
               
QuickUnsorted[idx] = RandObj.Next().ToString();
           
}

           
BubbleSorted = BubbleSort(BubbleUnsorted);
           
CombSorted = CombSort(CombUnsorted);
           
QuickSorted = QuickSort(QuickUnsorted);
       
}

       
public static string[] QuickSort(string[] unsorted)
       
{
           
long begin = System.DateTime.Now.Ticks;
            
           
System.Array.Sort(unsorted);

           
long end = System.DateTime.Now.Ticks;
           
long duration = (end - begin)/10000;
           
Console.WriteLine("Quick Sort Data Count " + Convert.ToString(unsorted.Length) + " " + duration.ToString() + " milliseconds elapsed");

           
return unsorted;
       
}

       
public static string[] BubbleSort(string[] unsorted)
       
{            
           
bool swap;
           
long begin = System.DateTime.Now.Ticks;
           
do
           
{
               
swap = false;
               
for (int i = 1; i < unsorted.Length; i++)
               
{
                   
if (Convert.ToInt64(unsorted[i - 1]) > Convert.ToInt64(unsorted[i]))
                   
{
                       
string tmp = unsorted[i - 1];
                       
unsorted[i - 1] = unsorted[i];
                       
unsorted[i] = tmp;
                       
swap = true;
                   
}              
               
}
                
           
} while (swap);
           
long end = System.DateTime.Now.Ticks;
           
long duration = (end - begin)/10000;
           
Console.WriteLine("Bubble Sort Data Count " + Convert.ToString(unsorted.Length) + " " + duration.ToString() + " milliseconds elapsed");
           
return unsorted;
       
}

       
public static string[] CombSort(string[] unsorted)
       
{
           
int gap = unsorted.Length;
           
int swap;

           
long begin = System.DateTime.Now.Ticks;

           
do
           
{
               
if (gap > 1)
               
{
                   
gap = (gap * 10) / 13;
                   
if (gap == 9 || gap == 10)
                   
{
                       
gap = 11;
                   
}
               
}

               
swap = 0;

               
for (int i = 0; i + gap <= unsorted.Length-1; i++)
               
{
                   
if (Convert.ToInt64(unsorted[i]) > Convert.ToInt64(unsorted[i+gap]))
                   
{
                       
string tmp = unsorted[i];
                       
unsorted[i] = unsorted[i+gap];
                       
unsorted[i+gap] = tmp;
                       
swap = 1;
                   
}                  
               
}

           
} while (gap > 1 || swap == 1);

           
long end = System.DateTime.Now.Ticks;
           
long duration = (end - begin) / 10000;
           
Console.WriteLine("Comb Sort Data Count " + Convert.ToString(unsorted.Length) + " " + duration.ToString() + " milliseconds elapsed");
           
return unsorted;
       
}
   
}
}

Colorized by: CarlosAg.CodeColorizer

 

배열의 개수를 파라미터로 넘길 수 있도록 코딩 했습니다. 그래서 테스트는 10개, 100개, 1000개 이렇게 10배씩 늘려나가는 방식으로 진행했고, 3번 해서 그 평균을 구했습니다. 결과는 이렇습니다. (단위는 millisecond 즉, 100분의 1초입니다)

  Bubble Sort Comb Sort Quick Sort
10 5 0 2
100 11 1 2
1000 804 18 5
10000 83904 282 42

확실히 Bubble Sort는 매우 매우 느립니다. ^^;; 그리고 Comb Sort는 100개 이하에서는 오히려 Quick Sort보다도 빠른데, 1000개 정도부터는 Quick Sort의 상대가 되질 않네요.

그래서 따로 Comb Sort 와 Quick Sort 만을 따로 비교를 해보았습니다. 이 두 가지 Sorting은 100000개와 1000000개도 테스트할 수 있었습니다. (Bubble Sort로는 도저히 이 숫자는 할 수가 없더군요)

 

  Comb Sort Quick Sort
10 0 2
100 1 2
1000 18 5
10000 282 42
100000 3831 526
1000000 49382 6418

그래도 빠르다고 생각했던 Comb Sort도 100000개 이상부터는 Quick Sort와 비교가 불가능한 성능을 보여줍니다. 정말 Quick Sort가 짱 이네요 ^^

 

Quick Sort에 대해서 자세히 알고 싶은 분들은 WikipediaQuick Sort 항목을 참조하시길 바랍니다. 오른 편에 있는 그림이 정말 Quick Sort의 모든 것을 보여주네요.

그리고 Reflector로 당연히 닷넷의 소스도 확인할 수가 있습니다. 아래 그림과 소스처럼 말이죠. ^^

internal SorterObjectArray(object[] keys, object[] items, IComparer comparer)
{
   
if (comparer == null)
   
{
       
comparer = Comparer.Default;
   
}
   
this.keys = keys;
   
this.items = items;
   
this.comparer = comparer;
}

internal void SwapIfGreaterWithItems(int a, int b)
{
   
if (a != b)
   
{
       
try
       
{
           
if (this.comparer.Compare(this.keys[a], this.keys[b]) > 0)
           
{
               
object obj2 = this.keys[a];
               
this.keys[a] = this.keys[b];
               
this.keys[b] = obj2;
               
if (this.items != null)
               
{
                   
object obj3 = this.items[a];
                   
this.items[a] = this.items[b];
                   
this.items[b] = obj3;
               
}
           
}
       
}
       
catch (IndexOutOfRangeException)
       
{
           
throw new ArgumentException(Environment.GetResourceString("Arg_BogusIComparer", new object[] { this.keys[b], this.keys[b].GetType().Name, this.comparer }));
       
}
       
catch (Exception exception)
       
{
           
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), exception);
       
}
   
}
}

internal void QuickSort(int left, int right)
{
   
do
   
{
       
int low = left;
       
int hi = right;
       
int median = Array.GetMedian(low, hi);
       
this.SwapIfGreaterWithItems(low, median);
       
this.SwapIfGreaterWithItems(low, hi);
       
this.SwapIfGreaterWithItems(median, hi);
       
object y = this.keys[median];
       
do
       
{
           
try
           
{
               
while (this.comparer.Compare(this.keys[low], y) < 0)
               
{
                   
low++;
               
}
               
while (this.comparer.Compare(y, this.keys[hi]) < 0)
               
{
                   
hi--;
               
}
           
}
           
catch (IndexOutOfRangeException)
           
{
               
throw new ArgumentException(Environment.GetResourceString("Arg_BogusIComparer", new object[] { y, y.GetType().Name, this.comparer }));
           
}
           
catch (Exception exception)
           
{
               
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), exception);
           
}
           
catch
           
{
               
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"));
           
}
           
if (low > hi)
           
{
               
break;
           
}
           
if (low < hi)
           
{
               
object obj3 = this.keys[low];
               
this.keys[low] = this.keys[hi];
               
this.keys[hi] = obj3;
               
if (this.items != null)
               
{
                   
object obj4 = this.items[low];
                   
this.items[low] = this.items[hi];
                   
this.items[hi] = obj4;
               
}
           
}
           
low++;
           
hi--;
       
}
       
while (low <= hi);
       
if ((hi - left) <= (right - low))
       
{
           
if (left < hi)
           
{
               
this.QuickSort(left, hi);
           
}
           
left = low;
       
}
       
else
       
{
           
if (low < right)
           
{
               
this.QuickSort(low, right);
           
}
           
right = hi;
       
}
   
}
   
while (left < right);
}}

 

Posted by kkongchi
C# & VB.NET2008. 12. 26. 22:22

 

* 아래 덧글에서 보실 수 있듯이, exedra님 지적 때문에 일부 소스와 비교 데이터가 수정되었습니다. (2008-01-14)

 

회사 솔루션 코드 중에 VB로 된 Comb Sort 코드가 있길래, C#으로 재 작성해보았습니다.

그러면서 가장 기본적인 Bubble Sort와 성능 차이가 얼마나 되나 비교도 해 봤습니다.

Comb Sort의 내용에 대해서는 Wikipedia의 Comb_sort 항목을 참조하시면 됩니다.

 

코드는 아래와 같습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SortingSample
{
   
class Program
   
{
       
static void Main(string[] args)
       
{
           
int dataCount = Convert.ToInt32 (args[0]);
           
string[] BubbleUnsorted = new string[dataCount];
           
string[] CombUnsorted = new string[dataCount];

           
string[] BubbleSorted = new string[dataCount];
           
string[] CombSorted = new string[dataCount];

           
Random RandObj = new Random();
           
for (int idx = 0; idx < dataCount; idx++)
           
{
               
BubbleUnsorted[idx] = RandObj.Next().ToString();
               
CombUnsorted[idx] = RandObj.Next().ToString();
           
}

           
BubbleSorted = BubbleSort(BubbleUnsorted);
           
CombSorted = CombSort(CombUnsorted);
       
}
        
       
public static string[] BubbleSort(string[] unsorted)
       
{            
           
bool swap;
           
long begin = System.DateTime.Now.Ticks;
           
do
           
{
               
swap = false;
               
for (int i = 1; i < unsorted.Length; i++)
               
{
                   
if (Convert.ToInt64(unsorted[i - 1]) > Convert.ToInt64(unsorted[i]))
                   
{
                       
string tmp = unsorted[i - 1];
                       
unsorted[i - 1] = unsorted[i];
                       
unsorted[i] = tmp;
                       
swap = true;
                   
}              
               
}
                
           
} while (swap);
           
long end = System.DateTime.Now.Ticks;
           
long duration = (end - begin)/10000;
           
Console.WriteLine("Bubble Sort Data Count " + Convert.ToString(unsorted.Length) + " " + duration.ToString() + " milliseconds elapsed");
           
return unsorted;
       
}
        
       
public static string[] CombSort(string[] unsorted)
       
{
           
int gap = unsorted.Length;
           
int swap;

           
long begin = System.DateTime.Now.Ticks;

           
do
           
{
               
if (gap > 1)
               
{
                    
                   
gap = (gap * 10) / 13;
                   
if (gap == 9 || gap == 10)
                   
{
                       
gap = 11;
                   
}
               
}

               
swap = 0;

               
for (int i = 0; i + gap <= unsorted.Length-1; i++)
               
{
                   
if (Convert.ToInt64(unsorted[i]) > Convert.ToInt64(unsorted[i+gap]))
                   
{
                       
string tmp = unsorted[i];
                       
unsorted[i] = unsorted[i+gap];
                       
unsorted[i+gap] = tmp;
                       
swap = 1;
                   
}                  
               
}

           
} while (gap > 1 || swap == 1);

           
long end = System.DateTime.Now.Ticks;
           
long duration = (end - begin) / 10000;
           
Console.WriteLine("Comb Sort Data Count " + Convert.ToString(unsorted.Length) + " " + duration.ToString() + " milliseconds elapsed");
           
return unsorted;
       
}
   
}
}

Colorized by: CarlosAg.CodeColorizer

 

1000건을 Sorting했을 때 결과는 이렇습니다.

BubblevsComb

차이가 매우 크다는 것을 보실 수 있습니다. Comb Sort가 꽤 빠른 Sorting 알고리즘이라는 것을 알 수가 있습니다. 다음 번에 기회가 나면, .NET의 기본 Sort 알고리즘인 Quick Sort와 한 번 비교해보도록 하겠습니다.

 

Tistory 태그: ,,,
Posted by kkongchi
C# & VB.NET2008. 3. 9. 21:01
Tistory 태그: ,

메서드를 작성할 때에, Argument가 다 정상적으로 들어올거라고 가정해서는 절대로 안 된다. 반드시 다음과 같이 Validation 코드를 작성해서, 메서드의 가장 위에 둘 필요가 있다.


1. Argument 가 null인지 검사해서, null이라면 NullArgumentException을 던져야 한다. 아래 코드는 .NET Framework의 System.Windows.Annotations.Annotation 클래스의 WriteXml메서드의 가장 윗부분 코드이다.


public void WriteXml(XmlWriter writer)
{
   
if (writer == null)
   
{
       
throw new ArgumentNullException("writer");
   
}
}
 

2. 각 argument값 자체의 이상 유무를 검사한다.
기본적인 string 이라면, 전에 올렸던 글처럼 IsNullOrEmpty 메서드를 써서 검사하고, 다른 type이라면.. -_-;; 다양한 것을 다 여기서 다루기는 힘드므로.. 일단 적절하게 검사한다.  아래 예제는 위에서도 사용한 System.Windows.Annotations.Annotation 클래스의 WriteXml메서드의 일부분인데, 넘어온 XML의 내부 스트링들을 IsNullOrEmpty메서드로 검사해서, 없을 경우 채워넣는 코드이다.


  if (string.IsNullOrEmpty(writer.LookupPrefix("http://schemas.microsoft.com/windows/annotations/2003/11/core")))
   
{
       
writer.WriteAttributeString("xmlns", "anc", null, "http://schemas.microsoft.com/windows/annotations/2003/11/core");
   
}
   
if (string.IsNullOrEmpty(writer.LookupPrefix("http://schemas.microsoft.com/windows/annotations/2003/11/base")))
   
{
       
writer.WriteAttributeString("xmlns", "anb", null, "http://schemas.microsoft.com/windows/annotations/2003/11/base");
   
}
}
Posted by kkongchi
C# & VB.NET2007. 2. 26. 11:48
어떤 메소드의 호출자 정보를 알고 싶을 때가 있다. 문제가 발생했는데 디버깅하기가 힘들 때, 이런 호출자 정보들을 로그 등에 기록해둔다면, 문제 해결에 유용하게 쓰일 수 있는 단서가 될 것이다.

System.Diagnostics.StackFrame, System.Diagnostics.StackTrace 객체를 사용해서 호출자 정보를 얻을 수가 있다.

string stackTraceString = null;

System.Diagnostics.StackTrace objStackTrace = new System.Diagnostics.StackTrace(new System.Diagnostics.StackFrame(1));

stackTraceString = objStackTrace.ToString();

return stackTraceString;


마지막에 return되는 값은 다음과 같이 생겼다. 이것이 바로 현재 메소드를 호출한 Caller Method이다.

   at StackFrameTest.Class3.class3Method(String param)



* StackFrame 클래스를 생성할 때 주는 파라미터 - Integer - 를 조정해서, 각 호출 스택 상의 레벨을 모두 추적할 수도 있다.
* 간단하게 ToString()을 하면 저렇게 나오게 되지만, StackFrame 객체에는 GetFileName,
GetFileLineNumber 등의 더 자세한 정보를 알 수 있는 메소드들도 있다.

Posted by kkongchi
C# & VB.NET2007. 2. 12. 15:33

현재, 몸담고 있는 회사의 솔루션 코드를 보다가 이런 것을 발견했다.

Imports System.Threading

Public Class Metadata
    Private myMetadata As New InsightMetadata

    Private Shared _singleton As Metadata
    Private Shared myInstanceMutex As New Mutex


    Private Sub New()
    End Sub

    Public Shared Function GetInstance() As Metadata

        myInstanceMutex.WaitOne()

        Try
            If _singleton Is Nothing Then
                _singleton = New Metadata
            End If

        Finally
            myInstanceMutex.ReleaseMutex()
        End Try

        Return _singleton

    End Function

    Public ReadOnly Property Appset(ByVal appsetID As String, ByVal app As String, ByVal userName As String, ByVal context As String, ByVal security As String) As Appset
        Get
            If (IsNothing(myMetadata.Appset(appsetID))) Then
                load(appsetID, app, userName, context, security)
            End If

            Return myMetadata.Appset(appsetID)
        End Get
    End Property

    Public Overloads Sub refresh(ByVal appset As String, ByVal app As String, ByVal userName As String, ByVal context As String, ByVal security As String)
        load(appset, app, userName, context, security)
    End Sub

    Private Function load(ByVal appsetID As String, ByVal app As String, ByVal userName As String, ByVal context As String, ByVal security As String) As Appset
        ...
    End Function
End Class

이 코드는 클래스 이름으로도 대충 짐작이 가듯이 전체 프로그램의 공용 Meta 데이터들을 저장해 놓는 모듈이다. 이런 메타 데이터들은 많은 모듈에서 사용하면서도 프로그램이 구동 중일 때는 거의 바뀌지 않는다는 속성이 있다. 그래서 이런 메타데이터는 메모리에 하나 올려놓고, 그것을 모두 사용하면 가장 좋을 것이다. 매번 읽어오지도 않고, 각 모듈에서 각각 호출하지도 않게 하는 최선의 방법일테니까 말이다.

싱글턴 패턴

이렇게 메모리에 딱 하나의 인스턴스를 올려놓을 필요가 있을 때 쓰는 것이 바로 "Singleton" 패턴이다. "Design Patterns"에 나오는 정의를 보자면 "ensure a class has only one instance, and provide a global point of access to it""한 클래스가 단지 하나의 인스턴스만을 가지게 하고, 그것에 액세스할 수 있는 글로벌한 포인트를 제공하는 것"이라고 되어 있다. 아래 위키 백과 페이지를 참조하면 더 자세하게 알 수 있다.

싱글턴 패턴 - 위키 백과

싱글턴 쓰임새

싱글턴 패턴은 쓰임새가 그렇게 다양하진 않다. 주로 위의 코드와 같은 메타 데이터, 혹은 파일 로깅 등의 공용 모듈에서 사용된다. (물론 위의 조건 - 하나의 인스턴스, 글로벌한 액세스 - 을 충족시킨다면 그 쓰임새 자체는 제한이 없다) 하지만 아래 글에서 볼 수 있듯이 이 Singleton 클래스는 다른 클래스들과 단단하게 결합되는(tightly-coupled) 경향이 있고, 이는 Unit Test를 어렵게 하고 전체적으로 각 모듈의 독립성을 저해하는 요소로 작용할 수도 있다. 아래와 같은 글들을 참고할 만 하다. (물론 주의깊게 읽어야 할 것이다. 단지 주장일 뿐이니)

IBM Developerworks: Use Your Singleton Wisely
PrestonLee.com: Singletons causes cancer

권장하는 닷넷 코드

MSDN: Exploring the Singleton Design Pattern

Implementing the Singleton Pattern in C#

MSDN의 Article도 괜찮긴 하지만, 2번째 글은 그야말로 총정리다. C#에서 가능한 모든 경우의 Singleton 구현에 대해서 모두 그 장단점을 서술해놓고 있다. 2번째 글을 보면 이 글의 처음에 나온 코드는 (MSDN 글을 봐도 그렇다) 그다지 좋지 못한 예라는 것을 알 수 있다..^^ 그래서 나도 현재 코드를 바꾸고 있는 중이다. 2번째 글의 네번째 버전으로(아래와 같이) 바꿀 예정이다. MSDN Article에 있는 코드와도 거의 같다고 보면 된다.

public sealed class Singleton
{
static readonly Singleton instance=new Singleton();

// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton()
{
}

Singleton()
{
}

public static Singleton Instance
{
get
{
return instance;
}
}
}

Posted by kkongchi
C# & VB.NET2006. 3. 28. 15:31

AES는 미국 정부에서 민감한 정보들을 암호화하는 데 사용되는 표준 암/복호화 알고리즘이다. 현재 업계 표준이고, 아직까지는 알려진 약점이 없는 가장 안전한 암/복호화 알고리즘이다. 최근에 일부 SI 프로젝트에서는 이 방식을 꼭 쓸 것을 요구하기도 한다.

1. 암호화

private static string EncryptString(string InputText, string Password)
{

  // Rihndael class를 선언하고, 초기화
  RijndaelManaged RijndaelCipher = new RijndaelManaged();

  // 입력받은 문자열을 바이트 배열로 변환
  byte[] PlainText = System.Text.Encoding.Unicode.GetBytes(InputText);

  // 딕셔너리 공격을 대비해서 키를 더 풀기 어렵게 만들기 위해서
  // Salt를 사용한다.
  byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());

  // PasswordDeriveBytes 클래스를 사용해서 SecretKey를 얻는다.
  PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(Password, Salt);

  // Create a encryptor from the existing SecretKey bytes.
  // encryptor 객체를 SecretKey로부터 만든다.
  // Secret Key에는 32바이트
  // (Rijndael의 디폴트인 256bit가 바로 32바이트입니다)를 사용하고,
  // Initialization Vector로 16바이트
  // (역시 디폴트인 128비트가 바로 16바이트입니다)를 사용한다.
  ICryptoTransform Encryptor = RijndaelCipher.CreateEncryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));

  // 메모리스트림 객체를 선언,초기화
  MemoryStream memoryStream = new MemoryStream();

  // CryptoStream객체를 암호화된 데이터를 쓰기 위한 용도로 선언
  CryptoStream cryptoStream = new CryptoStream(memoryStream, Encryptor, CryptoStreamMode.Write);

  // 암호화 프로세스가 진행된다.
  cryptoStream.Write(PlainText, 0, PlainText.Length);
 
  // 암호화 종료
  cryptoStream.FlushFinalBlock();

  // 암호화된 데이터를 바이트 배열로 담는다.
  byte[] CipherBytes = memoryStream.ToArray();

  // 스트림 해제
  memoryStream.Close();
  cryptoStream.Close();       

  // 암호화된 데이터를 Base64 인코딩된 문자열로 변환한다.
  string EncryptedData = Convert.ToBase64String(CipherBytes);

  // 최종 결과를 리턴
  return EncryptedData;
}

2. 복호화

private static string DecryptString(string InputText, string Password)
{
  RijndaelManaged  RijndaelCipher = new RijndaelManaged();

  byte[] EncryptedData = Convert.FromBase64String(InputText);
  byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());       

  PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(Password, Salt);

  // Decryptor 객체를 만든다.
  ICryptoTransform Decryptor = RijndaelCipher.CreateDecryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));

  MemoryStream  memoryStream = new MemoryStream(EncryptedData);           

  // 데이터 읽기(복호화이므로) 용도로 cryptoStream객체를 선언, 초기화
  CryptoStream  cryptoStream = new CryptoStream(memoryStream, Decryptor, CryptoStreamMode.Read);

  // 복호화된 데이터를 담을 바이트 배열을 선언한다.
  // 길이는 알 수 없지만, 일단 복호화되기 전의 데이터의 길이보다는
  // 길지 않을 것이기 때문에 그 길이로 선언한다.
  byte[] PlainText = new byte[EncryptedData.Length];       

  // 복호화 시작
  int DecryptedCount = cryptoStream.Read(PlainText, 0, PlainText.Length);               

  memoryStream.Close();
  cryptoStream.Close();

  // 복호화된 데이터를 문자열로 바꾼다.
  string DecryptedData = Encoding.Unicode.GetString(PlainText, 0, DecryptedCount);

  // 최종 결과 리턴
  return DecryptedData;
}


참고: http://dotnet.org.za/deon/articles/2998.aspx
Posted by kkongchi
C# & VB.NET2006. 3. 5. 17:56

C#에서는 try-catch-finally 구조를 사용해서 각 예외처리를 수행한다. 그런데 이 구조에서 예전에 제가 C#을 처음 익힐 때부터 약간 혼란스러웠던 것이 있다.


바로 Return 문이 어디에 위치해야 하는가이다...


MSDN 라이브러리의 try-catch-finally 구조에 대한 설명을 보자.

A common usage of catch and finally together is to obtain and use resources in a try block, deal with exceptional circumstances in a catch block, and release the resources in the finally block.

Catch finally같이 때의 일반적인 사용법은 try 블록에서 리소스를 얻어내서 사용하고, catch 블록에서는 예외적인 상황을 다루며, finally 블록에서리소스를 해제한다.



즉, return을 어디 두어야 하는 지는 어디에도 없다.

샘플도 교묘하게 void를 리턴 하는 함수를 만들어서 피해가고 있다.


두 가지 가설이 있을 수 있다.

첫 번째는 return은 메서드가 최종적으로 결과를 돌려주는 것이므로, finally 아래에 와야 한다.

두 번째는 try-catch-finally의 구조로 볼 때, return 문은 예외 처리 코드도 아니고 리소스 해제 코드도 아니기 때문에 try 내부에 오는 것이 맞다.

그래서 가지 방법으로 코드를 작성해 보았다.


//return문이 가장 마지막에 위치

public int ReturnInLastLine()

{

int i = 0;

PrivateOBJ obj = null;

try

{

obj = new PrivateOBJ();

i = obj.GetINT();

}

catch(Exception ex)

{

throw ex;

}

finally

{

obj = null;

}

return i;

}

//try블록 내부에 return이 위치

public int ReturnInTry()

{

int i = 0;

PrivateOBJ obj = null;

try

{

obj = new PrivateOBJ();

i = obj.GetINT();

           return i;

}

catch(Exception ex)

{

throw ex;

}

finally

{

obj = null;

}

}

그리고, 클래스를 컴파일한다음  .NET Reflector디스어셈블한 코드를 보았다.



결과는놀랍게도,

public int ReturnInLastLine()
{
int num1 = 0;
PrivateOBJ eobj1 = null;
try
{
eobj1 = new PrivateOBJ();
num1 = eobj1.GetINT();
}
catch (Exception exception1)
{
throw exception1;
}
finally
{
eobj1 = null;
}
return num1;
}

public int ReturnInTry()
{
int num2;
int num1 = 0;
PrivateOBJ eobj1 = null;
try
{
eobj1 = new PrivateOBJ();
num1 = eobj1.GetINT();

//여기 주의

//원 소스 코드에는 여기에 return이 있다. 그런데?
num2 = num1;
}
catch (Exception exception1)
{
throw exception1;
}
finally
{
eobj1 = null;
}

//여기에 리턴문이 있다.
return num2;
}


IL 코드도 이와 거의 유사하다고 보면 된다.

즉, return을 어느 위치에 두던지 간에 실제로 IL로 컴파일된 코드에서는 return이 가장 마지막에 위치한다는 것이다.

그래서 두 번째 try 내부에 리턴이 있는 경우를 보면 IL 코드는 내부적으로 (코드에는 없는) 변수를 하나
더 선언해서
거기에 값을 할당하고 그 변수를 최종적으로 리턴을 수행할 때 사용하는 것을 볼 수가 있다.



결론

1. 코드 상에서 return문이 어디에 있더라도, IL 코드 상에서는 가장 마지막 라인에 return문이 있게 된다. 결론적으로 마지막에 두는 것이 맞다는 것이다.

2. 코드에 Try 블록 혹은 catch 블록 혹은 여러 군데에 return을 위치시킬 수 있다. 심지어 if-else 문을 사용해서 각각 return하는 경우도 있다. (절대로 권장하지 않는 코딩방법!!) 그 경우 IL이 내부적으로 최종 리턴을 위해서 변수를 하나씩 더 할당한다. 이 값이 만약 매우 큰 데이터셋이라면? 메모리 누수의 원인이 될 수도 있을 듯 하다. 결과적으로 비효율적인 코드라는 것이고, 피해야 할 코딩이 되는 것이다.

Posted by kkongchi