출처 : http://blog.tobegin.net/39 / Writer by 정은성
07| 공변성과 반공변성(Covariance and Contravariance) 구독자 여러분 안녕하세요 오늘 다뤄볼 내용은 공변성과 반공변성입니다. " 공변성, 반 공변성은 도대체 뭘까? " Variance & ContraVariance 두단어를 볼 때 무슨 소리인지 알 수가 없을것입니다. 물론 수학을 많이 다뤄보셨다면 무슨 의미인지 알 수는 있지만 대부분의 개발자분들은 무심코 지나치셨거나 모른체 사용을 해왔을 것 같네요. 공변성, 반공변성은 수학에서 정의한 원리를 바탕으로 부모는 자식을 받아 드일수 있으나 자식은 부모를 받아 드릴 수 없는 원칙을 말하고 있습니다. 즉, 공변성은 원래 지정된 것 보다 더 많은 파생을 사용할수 있는 이며, 반공변성은 더 작은 파생 형식을 사용 할 수 있는 기능을 뜻합니다. 말이 어렵죠? 이해를 돕고자 일반적인 프로그래밍 이야기를 해보도록 하겠습니다. 클래스의 상속은 부모 클래스로부터 상속을 받아 메소드나 멤버 변수를 그대로 물려 받을 수 있을 텐데요. 자식은 양적으로 부모보다 더 많이 가지게 되며 반면에 부모클래스는 상속받은 자식과 비교를 한다면 적게 가진다는 결론을 내릴수 있습니다.
그렇다면 부모 클래스에 자식 클래스를 캐스팅 한다면 어떻게 될까요? 다형성 원칙에 의해 부모 클래스를 가지고 있는 기능을 제외한 추가된 자식의 기능은 무시가 되버리고 어쨌든 문제는 없어 보입니다. 반대로 자식을 부모에 대입하려고 한다면 형 변환에 대한 예외에러가 출력이 되겠죠. 지금부터 Genric covariance And Contra-Variance에 대해 설명을 하려면 그동안 어떻게 개선이 되고 있었는지 살펴보아야할것 같습니다. * Variance in Arrays (C# 1.0) C# 1.0에서부터 variance in Arrays라는 개념이 등장하게 되었습니다. [코드 1] Variance in Arrays (C# 1.0) Sample public class Animal { ... }
public class Lion : Animal { ... } public class Deer : Animal { ... } public class Cat : Animal { ... } Animal[] animals = new Lion[3]; Animals[0] = new Deer(); // Runtime시 Exception 발생 현재 보시는 코드는 전형적인 상속이 되는 코드입니다. 1.0에서는 위와 같이 Reference 타입을 할당하려고 할 때 covariance라고 하였습니다. Animal로 부터 상속 받은 사자와 사슴과 고양이는 Animal 스택틱 영역에 객체로 할당 할 수 있습니다. 여기서 살펴보셔야할 내용은 마지막 두번째 줄 코드인데요. Animals 인스턴스에 사자를 할당을 하려고 하고 있는데. 이어서 마지막 코드에서 사자 우리에 사슴을 할당하려고 합니다. 일반적으로 사자 우리에 사슴을 넣으면 안되잖아요. 그럼 잡혀먹히잖아요...흑흑..ㅠㅠ 이처럼 C# 1.0 버전에서는 타입에 대한 불확실한 요소에 대해 보호 할 수 없었습니다. * Variance in Delegate-Member Association(C# 2.0) 그렇게 C# 2.0에서 제네릭과 대리자가 도입이 되면서 이전 버전의 불안정한 요소를 벗어 날 수 있게 되었습니다. 이들을 통해서 컴파일 타임에서 엄격한 체크가 수행이 되었고 안전한 코드를 작성 할 수 있게 되었습니다. 하지만 이때부터 공변성과 반공변성은 애매모한 관계를 가지게 되었죠. 코드를 보시면서 좀더 이해를 해보도록 하겠습니다. [코드 2] Variance in Delegate-Member Association(C# 2.0) Sample class Mammals { ... }
class Dogs : Mammals { ... } public delegate Mammals HandlerMethod(); public static Mammals FirstHandler() { return null; } public static Dogs SecondHandler() { return null; } public Form1() { InitializeComponent(); 1).... HandlerMethod handler = SecondHandler; 2).... this.textBox1.KeyDown += this.MultiHandler; 3).... this.button1.MouseClick += this.MultiHandler; } private void MultiHandler(object sender, System.EventArgs e) { ... } 현재 코드에서는 1번의 경우가 공변성이 되며, 2,3번의 경우는 반공변성이 되는 예제입니다. * 1번의 경우 대리자를 사용한 경우입니다만 대리자 자신이 포함 할 수 있는 형식인 Mammals에 대해서만 해석을 할 수 가 있기 때문에 공변성 이락 할수 있습니다.
* 2,3번의 경우 이미 선언된 형식에 호환성을 맞추기 위해서 역으로 해석한 모습을 볼 수가 있는데요. EventArgs로부터 KeyEventArgs와 MouseEventArgs로 역으로 호환성을 맞추려고 하기 때문에 반공변성이 될 수 있습니다.
* Variance in Generic Delegate (C# 3.0) 그렇게 2.0에서 애모한 관계를 개선하고자 3.0이 등장을 하게 되었는데요. 이때는 제네렉과 대리자를 통해 공변성이 많이 지원이 되었습니다. 이말은 상위 클래스가 존재를 한다면 하위 클래스가 상위 클래스를 모두 가져 올 수 있도록 IEnumerable 인터페이스를 사용할수 있게 되어 covariance를 지원 하게 되었습니다. 지원을 하였지만 아래코드를 보시면 공변성에 대한 모순이 발생을 하게 되었습니다. [코드 3] Variance in Generic Delegate (C# 3.0) Exception Sample IList<string> strings = new List<string>();
IEnumberable<object> objects = strings; ... 1) // Exception 발생 var result = string.Union(objects); ... 2) // Exception 발생 1번과 2번의 경우 공변성을 하려고 하는데 Exception이 발생되는 모습을 볼 수 있었습니다. * Generic Covariance And Contra Variance(C# 4.0) 3.0에서 발생하게 된 이슈를 해결 하기 위해 4.0에서는 Generic Covariance And Contra Variance이 등장하게 됩니다. 코드를 보시면 새로운 형식을 볼 수 있을텐데요. [코드 4] Generic Covariance And Contra Variance(C# 4.0) Sample class Animal {}
class Cat : Animal { } class Program { delegate void ActionDemo<in T>(T a); delegate T FuncDemo<out T>(); static void Main() { //Contra-Variance ActionDemo<Animal> act1 = (ani) => { Console.WriteLine(ani); }; ActionDemo<Cat> cat1 = act1; cat1(new Cat()); //CoVariance FuncDemo<Cat> cat = () => new Cat(); FuncDemo<Animal> animal = cat; Console.WriteLine(animal()); } } 3.0에서 모순이 된 IEnumberable 인터페이스의 문제를 해결하면서 애모했던 공변성과 반공변성에 대해 새로운 형식인 IN, OUT 키워드를 도입하면서 관계를 해소 할 수 있게 되었습니다. - Contra-Variance를 하시려면 IN 키워드를 사용
- Co-Variance를 하시려면 OUT 키워드를 사용 그외 인터페이스와 델리게이트는 다음으며 MSDN 문서를 참고 바랍니다. * InterFaces - System.Collections.Generic.IEnumberable<out T>
- System.Collections.Generic.IEnumberator<out T> - System.Linq.IQueryable<out T> - System.Collections.Generic.IComparer<in T> - System.Collections.Generic.IEqualityComparer<in T> - System.IComparable<int T> * Delegates - System.Func<int T, ..., out R>
- System.Action<in T, ...> - System.Predicate<in T> - System.Comparison<in T> - System.EventHandler<in T> [소스 참고]Hello .NET Framework 4 세미나 [세션3] 발표자료와 소스 : http://blog.tobegin.net/35 참고 문헌 1. PDC2008 : The Future of C# - Anders Hejlsberg : http://channel9.msdn.com/pdc2008/TL16/ 2. New Features in C# 4.0 - Mads Torgersen, C# Language PM, April 2010 : http://msdn.microsoft.com/ko-kr/vcsharp/ff628440(en-us).aspx 3. 제네릭의 공변성(Covariance)과 반공변성(Contravariance) : http://msdn.microsoft.com/ko-kr/library/dd799517.aspx 포스팅을 마치며... 유익한 포스팅이 되셨는지 모르겠네요. 더 궁금하신점이 있으시면 댓글 혹은 http://blog.tobegin.net/notice/1 프로필정보를 통해 문의 바랍니다. 감사합니다. 정은성 드림 PS. 이번주는 태풍이 온다네요. 우산 꼭 챙기세요 ^ㅁ^
출처 : http://blog.tobegin.net/39 / Writer by 정은성 |
C#
- [C# 4.0] New Features in C# : 07. 공변성과 반공변성(Covariance and Contravariance) 2010.10.12
- [C# 4.0] New Features in C# : 06. COM특성 상호운용 개선사항(Com-specific interop features) 2010.10.12
- [C# 4.0] New Features in C# : 05. 명명된 인수와 선택적 인수(Optional and Named Parameters) 2010.10.12
- [C# 4.0] New Features in C# : 04. dynamic 유형 객체 #2 : Dynamic Lookup 2010.10.12
- [C# 4.0] New Features in C# : 03. dynamic 유형 객체 #1 : DLR 2010.10.12
- [C# 4.0] New Features in C# : 02. C# 개요 및 배경 2010.10.12
- [C# 4.0] New Features in C# : 01. C# 프로그래밍 동향 2010.10.12 2
- C#2.0 에 추가된 자료형 Generics 2010.03.03
- C#의 string 타입과 StringBuilder 비교 2008.12.24
- C# 코딩 연습 – 인터페이스의 명시적 구현 2008.12.24
- .NET Framework 정규표현식 자습서 2008.12.24
- 라면 만들기를 적용한 예 2008.12.24
- 라면만들기 2008.12.24
- Boxing과 Unboxing 2008.12.11
- Boxing과 Unboxing의 모든 것 2008.12.09
[C# 4.0] New Features in C# : 07. 공변성과 반공변성(Covariance and Contravariance)
[C# 4.0] New Features in C# : 06. COM특성 상호운용 개선사항(Com-specific interop features)
출처 : http://blog.tobegin.net/38 / Writer by 정은성
(COM-Specific Interop. Improvements)
구독자 여러분 안녕하세요
이번 다뤄볼 내용은 바로 COM특성 어떻게 향상 되었는지 개선사항에 대해 알아보도록 하겠습니다.
흔히 오피스 오토메이션 프로그래밍을 할때 Office API에 관련된 PIA를 등록하여 실행 때마다 PIA에 맵핑되는 타입으로 형변환을 해주어야하며 성능상으로 좋지 않았습니다.
C# 4.0에서는 가져온 PIA 형식 정보 대신 포함된 형식 정보를 응용 프로그램에 포함시켜 배포를 할수 있게 되었습니다.
코드를 보시면서 살펴보도록 하겠습니다.
[코드 1] COM-Specific Interop Sample Fx4 이하
Excel.Workbook workBook =
excel.Workbooks.Open("filename.xlsx", missing, missing, missing, missing,
missing, missing, missing, missing, missing, missing, missing, missing, missing,missing); Excel.Worksheet workSheet = (Excel.Worksheet)workBook.ActiveSheet;
지금보시는 코드[코드 1]는 MS 오피스 프로그램의 오토메이션 되는 코드 조각입니다.
코드를 보시면 단순히 시트 하나를 Open 할 뿐인데 코드가 매우 복합한데다가 알 수 없는 타입과 함께 작성을 해줘야 하는 모습을 볼 수 있습니다. 전체 코드 작성된 기준으로 본다면 WorkSheet를 포함하여 ExcelApplication.WorkBook등 모두 기술을 했다면 코드가 생각보다 길어질것입니다.
또한 코드가 길어지는건 두번째로 볼때 코딩을 작성하다보면 도대체 이 인수가 어떻게 사용되는지 모를 뿐만 아니라 사용하지 않는 파라메터에 대해서는 ref 처리를 해줘야하는 불편한 개발을 할 수 밖에 없었는데요.
기존 VB개발자시라면 " 아 불편해!!! " 라고 생각이 드실겁니다.
그렇다면 Com 상호운용성이 어떻게 개선이 됬는지 확인 해보도록 하겠습니다.
[코드 2] COM-Specific Interop Sample Fx4
Excel.Worksheet workSheet =workBook.ActiveSheet;
와우 많고 불필요한 코드들이 단 두 줄로 줄어들었습니다. Option Parameters기능을 활용하여 정말 간소화 된 모습을 볼 수 있는데요. 어떤 내용들이 개선이 되었는지 스펙을 확인해보도록 하겠습니다.
* COM 상호 운용의 개선된 사항
- Optional and Named Parameters
- Indexed properties
- Optional “ref” modifier
- PIA embedding (“NO-PIA”)
COM 상호운용성이 위와 같이 개선이 되었습니다. 우선 Automatic Object 방법이 Dynamic mapping으로 변경이 되었고
Optional and named Parameters 기능이 지원함으로써 메소드가 간소화 된 모습을 볼수 있습니다.이 말은 이전에 사용하지 않던 매개변수에 대해서 ref missing을 처리를 해주었는데요. 이제는 사용을 안하셔도 된다는 이야기입니다. 그리고 기존의 오피스 프로그래밍을 하시다보면 PIA라는 인터페이스의 정의를 참조하여 호출을 하였고 어떤 인터페이스를 사용 할지 알수 없었기 때문에 형변환을 해줘야하는 지져분한 코드를 사용해야 만 했었고 더구나 성능상으로 좋지 않았습니다.
이제는 컴파일 타임에서 PIA를 직접적으로 사용하지 않으셔도 됩니다. PIA형식 정보를 응용프로그램에 포함 시키는 기능이 새롭게 추가 되었습니다. 즉, 해당 어셈블리에 대해서 옵션을 지정 할 수 있는데요.
Embed introp Type 을 지정해주므로써 필요한 PIA 인터페이스를 코드에 삽입하므로써 COM 객체와 좀더 퍼펙트한 상호 운용이 가능하게 되었습니다.
그럼 간단한 코드를 통해서 알아보도록 하겠습니다.
[코드 3] COM-Specific Interop Sample Fx4 이하
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
namespace HelloFx4_3_OfficeAutomaticSample
{
class Program
{
static void Main(string[] args)
{
var excel = new Excel.Application();
excel.Workbooks.Add(Type.Missing);
excel.Visible = true;
((Excel.Range)excel.Cells[1, 1]).Value2 = "Process Name";
((Excel.Range)excel.Cells[1, 2]).Value2 = "Memory Usage";
var processes =
from p in Process.GetProcesses()
orderby p.WorkingSet64 descending
select p;
int i = 2;
foreach (var p in processes.Take(10))
{
((Excel.Range)excel.Cells[i, 1]).Value2 = p.ProcessName;
((Excel.Range)excel.Cells[i, 2]).Value2 = p.WorkingSet64;
i++;
}
Excel.Range range = (Excel.Range)excel.Cells[1, 1];
Excel.Chart chart = (Excel.Chart)excel.ActiveWorkbook.Charts.Add(
Type.Missing, excel.ActiveSheet, Type.Missing, Type.Missing);
chart.ChartWizard(
range.CurrentRegion,
Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing,
"Memory Usage in " + Environment.MachineName, Type.Missing,
Type.Missing, Type.Missing);
chart.CopyPicture(Excel.XlPictureAppearance.xlScreen,
Excel.XlCopyPictureFormat.xlBitmap,
Excel.XlPictureAppearance.xlScreen);
var word = new Word.Application();
word.Visible = true;
var missing = Type.Missing;
word.Documents.Add(ref missing, ref missing, ref missing, ref missing);
word.Selection.Paste();
}
}
}
[코드 3-1] (코드3) 코드 조각
excel.Workbooks.Add(Type.Missing);
excel.Visible = true;
((Excel.Range)excel.Cells[1, 1]).Value2 = "Process Name";
((Excel.Range)excel.Cells[1, 2]).Value2 = "Memory Usage";
코드를 보시면 excelApplication를 인스턴스화 하여 위에 WorkBooks를 추가하였고 셀 1,1과 1,2에 문자열을 대입하였습니다.
[코드 3-2] (코드3) 코드 조각
from p in Process.GetProcesses()
orderby p.WorkingSet64 descending
select p;
int i = 2;
foreach (var p in processes.Take(10))
{
((Excel.Range)excel.Cells[i, 1]).Value2 = p.ProcessName;
((Excel.Range)excel.Cells[i, 2]).Value2 = p.WorkingSet64;
i++;
}
그리고 현재 컴퓨터의 운영중인 프로세스를 조회한 다음 링큐와 enumable 타입을 사용하여 각 셀에 바인딩하는 모습도 볼수 가 있습니다. 다음으로는 차트 생성 메소드를 활용하여 차트를 생성하고 새로운 워드 문서에 차트 이미지를 복사를 하는 데이터 플로우 입니다.
[코드 3-3] (코드3) 코드 조각
Type.Missing, excel.ActiveSheet, Type.Missing, Type.Missing);
chart.ChartWizard( range.CurrentRegion, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
"Memory Usage in " + Environment.MachineName, Type.Missing, Type.Missing, Type.Missing);
chart.CopyPicture(Excel.XlPictureAppearance.xlScreen,
Excel.XlCopyPictureFormat.xlBitmap,
Excel.XlPictureAppearance.xlScreen);
var word = new Word.Application();
word.Visible = true;
var missing = Type.Missing;
word.Documents.Add(ref missing, ref missing, ref missing, ref missing);
word.Selection.Paste();
지금부터 개선된 특징을 적용 시켜야 할텐데요. 우선 그 대상이 무엇인지 살펴보면서 코드를 변경 해보도록 하겠습니다.
여러분들은 코드 3-1, 3-2, 3-3에서 블록 처리 되어 있는 코드를 살펴보시면 됩니다.
우선 [코드3-1]에 workbook에 Add 메소드에 매개변수가 그 변경 대상이 되며 다음으로는 형변환을 포함하여 Value2 프로퍼티가 변경 대상이 되며 다음과 같이 수정을 할 수 있습니다.
[코드 4-1] (코드 3-1)을 Fx4 특징으로 변경
excel.Visible = true;
((Excel.Range)excel.Cells[1, 2]).Value2 = "Memory Usage";
excel.Cells[1, 1].Value = "Process Name";
excel.Cells[1, 2].Value = "Memory Usage";
[코드3-2]에서 foreach문 내부의 range 부분도 또한 다음과 같이 수정이 가능합니다.
[코드 4-2] (코드3-2)을 Fx4 특징으로 변경
from p in Process.GetProcesses()
orderby p.WorkingSet64 descending
select p;
int i = 2;
foreach (var p in processes.Take(10))
{
excel.Cells[i, 1].Value = p.ProcessName;
excel.Cells[i, 2].Value = p.WorkingSet64;
i++;
}
[코드3-3] 차트에서도 형변환을 포함하여 Add 메소드에 매개변수인 missing, activeSheet, Missing, Missing 인자들도 그 변경 대상이 됩니다. 표현하고자 하는 데이터는 두번째 매개변수이며 파라미터 명칭을 찾아보시면 After이름을 가졌네요.
[그림 1] (코드3-3) Charts.Add 인첼리스트
[코드 4-3] (코드3-3)을 Fx4 특징으로 변경
Type.Missing, excel.ActiveSheet, Type.Missing, Type.Missing);
Excel.Chart chart = excel.ActiveWorkbook.Charts.Add(After: excel.ActiveSheet);
코드를 다시 내려다 보시면 알수 없는 타입이 선언 되어 있는데요.
(Type.Missing, Type.Missing, Type.Missing, Type.Missing....) 이 코드 또한 변환 대상이 됩니다.
똑같이 적용을 해보면 다음과 같습니다.
[코드 4-4] (코드3-3)을 Fx4 특징으로 변경
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
"Memory Usage in " + Environment.MachineName, Type.Missing, Type.Missing, Type.Missing);
[그림 2] (코드3-3) chart.ChartWizard 인첼리스트
다시 아래 코드를 보시면 새로운 워드 문서를 추가하는 코드를 볼수 있습니다.
missing 타입을 ref 형태로 전달을 하기에 이것 또한 변경을 해줘야겠죠?
[코드 4-5] (코드3-3)을 Fx4 특징으로 변경
word.Visible = true;
word.Documents.Add(ref missing, ref missing, ref missing, ref missing);
word.Documents.Add();
word.Selection.Paste();
이렇게 코드를 다시 정리를 해보면 다음과 같습니다.
[코드 5] COM-Specific Interop Sample Fx4
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
namespace HelloFx4_3_OfficeDynamicMappingSample
{
class Program
{
static void Main(string[] args)
{
var excel = new Excel.Application();
excel.Workbooks.Add();
excel.Visible = true;
excel.Cells[1, 1].Value = "Process Name";
excel.Cells[1, 2].Value = "Memory Usage";
var processes =
from p in Process.GetProcesses()
orderby p.WorkingSet64 descending
select p;
int i = 2;
foreach (var p in processes.Take(10))
{
excel.Cells[i, 1].Value = p.ProcessName;
excel.Cells[i, 2].Value = p.WorkingSet64;
i++;
}
Excel.Range range = excel.Cells[1, 1];
Excel.Chart chart = excel.ActiveWorkbook.Charts.Add(After: excel.ActiveSheet);
chart.ChartWizard(Source: range.CurrentRegion, Title: "Memory Usage in " + Environment.MachineName);
chart.CopyPicture(Excel.XlPictureAppearance.xlScreen,
Excel.XlCopyPictureFormat.xlBitmap,
Excel.XlPictureAppearance.xlScreen);
var word = new Word.Application();
word.Visible = true;
word.Documents.Add();
word.Selection.Paste();
}
}
}
정리를 하였지만 아직 PIA 인터페이스를 응용 프로그램에 포함시키지 않았습니다.
내장하려면 참조되고 있는 어셈블리어 Excel, word, Core등의 어셈블리어를 선택하시고 프로퍼티에 Embed Interop Type 옵션을 True로 변경 해야합니다.
[그림 3] Embed Interop Type 설정
이렇게 True로 설정하게 되면 컴파일 타임에서 등록해야하는 레지스터리를 사전에 코드에 내장을 하여 불필요한 타입 패턴을 일치 하실 필요가 없으며 런타임 시점에서 dynamic mapping을 하게 됩니다.
[소스 참고]Hello .NET Framework 4 세미나[세션3] 발표자료와 소스 : http://blog.tobegin.net/35
참고 문헌
1. PDC2008 : The Future of C# - Anders Hejlsberg
: http://channel9.msdn.com/pdc2008/TL16/
2. New Features in C# 4.0 - Mads Torgersen, C# Language PM, April 2010
: http://msdn.microsoft.com/ko-kr/vcsharp/ff628440(en-us).aspx
3. 방법: Visual C# 2010 기능을 사용하여 Office Interop 개체에 액세스(C# 프로그래밍 가이드)
: http://msdn.microsoft.com/ko-kr/library/dd264733.aspx
포스팅을 마치며...
짧게 리뷰를 해보았는데요. 어떠셨는지 모르겟어요 ^^;
제법 강력해졌다라고 표현을 해드리고 싶습니다. 또한 NO-PIA 옵션으로 하위 버전에서 해당 구문이 실행이 가능하다는 사실 잊지마시구요 이제 NO-PIA 옵션을 활용 해보세요 : )
더 궁금하신점이 있으시면 댓글 혹은 http://blog.tobegin.net/notice/1 프로필정보를 통해 문의 바랍니다.
다음 포스팅은 공변성과 반공변성(Co-Variance and Contra-Variance)에 대해서 살펴보도록 하겠습니다.
감사합니다.
정은성 드림
PS. 이번주는 태풍이 온다네요. 우산 꼭 챙기세요 ^ㅁ^
[C# 4.0] New Features in C# : 05. 명명된 인수와 선택적 인수(Optional and Named Parameters)
출처 : http://blog.tobegin.net/37 / Writer by 정은성
(Optional and Named Parameters)
구독자 여러분 안녕하세요
오랜만이시죠? ^^ 이제 저녁 온도가 제법 차가워지고 있네요.. 가을이 오려나봅니다.
건강에 유념하시길 바라구요 바로 포스팅 시작하도록 하겠습니다.
지난 시간에 이어서 오늘 다뤄볼 내용은 바로 명명된 인수와 선택적 인수(Optional and Named Parameters) 입니다. 누구나 개발을 하시다보면 종종 긴 파라미터를 정의를 해야할 때가 있는데요. 그 시그니처를 일치시키기 위해서 그 순서를 맞추려고 노력을 하거나 COM Interop에서 의미 없는 값을 전달을 하기 위해서 불필요한 코딩을 반복해왔습니다.
C# 4.0에서는 이러한 불필요한 코딩을 기본값을 설정을 하여 생략할 수 있게 개선이 되었는데요.
다음 예제 코드를 살펴보시면서 어떻게 개선이 되었는지 살펴보도록 하겠습니다.
[코드 1-1] Optional and Named Parameters Sample
1) M(1, 2, 3);
2) M(1, 2);
3) M(1);
4) M(1, z:3);
5) M(x:1, z:3);
6) M(z:3, x:1);
코드를 보시면 " 나는 저 코드와 유사하게 사용을해봤어 " 라고 생각을 하실분이 계씰것 같습니다.
구독자분들 중에 아이폰 어플리케이션을 개발을 해보신분이시거나 플렉스를 개발해본 경험이 있으시다면 바로 4~6번 코드와 같이 Named Parameter 방식으로 오브젝트를 활용을 해보신 경험이 있으실것 같습니다.
이기능은 두 언어로부터 유례된 것은 아니지만 앞서 소개해드린 Named Parameter,Optional parameter 방법이 개선이 되면서 어플리케이션 개발에 있어 코드 구성이 간결해지고 읽기 쉬운 코드를 작성이 가능해 졌습니다.
지금 부터 여러분들은 블록(색깔 블록)으로 처리한 부분을 유심히 살펴보실 보셔야합니다.
코드를 보시면 기존의 C# 코드와 다르게 매우 독특한 의미를 가지고 있는데요.
[코드 1-2] Optional and Named Parameters Sample
매소드 문장을 보시면 파라미터 타입을 정의하고 파라미터 이름인 int y와 int z에 5와 7의 숫자를 초기화하는 모습을 볼 수 있습니다. 기존에 볼 수 없었던 문장이지만 정의된 것과 같이 int y와 int z는 optional parameter를 정의하여 초기화 함으로써 옵셔널하게 그 매개변수나 메소드를 사용할 수 잇게 되었습니다. 무슨뜻이냐하면 일반적으로 객체를 생성하여 초기화 하거나 같은 이름을 가진 메소드를 정의를 할 수 있는데요. 보시는 코드와 같이 호출 지점에 대해서 컴파일을 하려고 할때 심덱스 에러가 나타나지 않으려고 한다면 같은 이름을 가진 메소드를 5번 정의를 해야합니다.
이제 부터는 Optional parameter를 활용하여 메소드가 5개 형태가 아니라
하나로 원하는 시그니처 만큼 표현이 가능해졌습니다.
[코드 1-3] Optional and Named Parameters Sample
1) M(1, 2, 3);
2) M(1, 2);
3) M(1);
두번째 코드는 객관적으로 보실 때 가장 일반적인 메소드 형태 이거나 오버로딩이 가능한 메소드 형태들인데요. 원래는 각각의 시그너처 별로 메소드를 정의를 해야하는데요. 이것 또한 Optional Parameter방법을 정의 함으로써 매개변수를 생략을 할 수 있고 생략을 하더라도 매개변수의 값을 전달 할수 있습니다.
[코드 1-4] Optional and Named Parameters Sample
4) M(1, z:3);
5) M(x:1, z:3);
6) M(z:3, x:1);
마지막 코드를 보시면 파라미터 명칭에 무언가를 주입하려고 하고 있는데요
C# 4.0에서는 Named Parameter 방법이 지원을 함으로써 매개변수의 순서와 관계없이 데이터를 바인딩 할수 있게 되었습니다. 결과를 보시면서 알아보도록 하겠습니다.
[결과 1] Optional and Named Parameters
1) M(1, 2, 3); // 1, 2, 3
2) M(1, 2); // 1, 2, 7
3) M(1); // 1, 5, 7
4) M(1, z:3); // 1, 5, 3
5) M(x:1, z:3); // 1, 5, 3
6) M(z:3, x:1); // 1, 5, 3
이미 말씀 드린것과 같이 매개변수가 순서와 관계없이 데이터를 바인딩 할 수 있다고 말씀을 드렸습니다.
4~6번 코드는 매개변수 z와 x에 대해서 명시적으로 할당을 하고 있습니다. 그래서 결과는 위와 같습니다.
아직 이해를 하지 못하셨다면 또 다른 예제를 준비 하였는데요.
이번에는 구독자 분께서 컴파일러가 되어 어떤 호출을 할 때 어떤 메소드가 호출이 될지를 이해하고자 준비해보았습니다.
다음 예제코드를 보시면서 알아보도록 하죠.
[코드 2-1] Optional and Named Parameters
2) M(object x) { … }
3) M(int x, string y= "hello") { … }
4) M(int x) { … }
위 예제코드를 보시면 4가지 오버로딩이 가능한 후보들이 있습니다.
그리고 이때 Caller는 다음과 같습니다.
[코드 2-2] Optional and Named Parameters
이제 컴파일러 입장에서 유추를 해봐야할 것 같은데요. 어떤 메소드가 어떻게 호출이 되어질까요?
제가 하나씩 풀이를 해보도록 하겠습니다.
1) 메소드는 y를 생략을 할수 있지만 x 타입과 일치하지 않기 때문에 후보 대상이 될 수 없습니다.
2) 메소드는 object 타입으로 데이터를 받아도 무방하기 때문에 후보가 됩니다.
3) 메소드는 x 타입과 매칭이 되고 y 매개변수는 생략이 가능하기 때문에 이것 또한 후보가 됩니다.
4) 메소드는 x타입과 매칭이 되기 때문에 후보가 됩니다.
다시 정리를 하자면 1) 메소드는 후보 대상이 될 수 없기 때문에 판단 기준에서 제외가 될것이며 2~4번 메소드 중에서 호출이 되어질텐데요. 가장 유력한 후보는 바로 4) 메소드입니다 이유는 타입이 가장 최적화된 시그니처를 가지고 있기 때문인데요.
2) 메소드는 Object로 받을수 있지만 Boxing / UnBoxing을 하기 때문에 성능상으로 좋아 보이지 않으므로 후보대상이지만 3~4번 메소드보다 좋지 않습니다. 3) 메소드는 Optinal parameter에 의해 생략이 가능하기 때문에 후보 대상이지만 아무래도 시그니처 패턴이 퍼펙트한 4번이 호출 될수 밖에 없습니다.
즉, 컴파일러는 호출하려고 하는 시점에서 파라미터가 온 결과를 보고 가장 최적화된 시그니처를 검사를 하게 됩니다. 만약 검사에서 성립이 되지 않으면 옵셔널 파라미터에 의해 메소드를 호출하게 됩니다.
Named and Optinal Parameters에서 단순히 코드를 줄여주는 역할 뿐만 아니라 가독성을 높여줍니다.
적절한 사례가 될지 모르겠지만 상황을 들어보도록 하겠습니다.
구독자 여러분들이 이 상황에서 어떻게 하시겠습니까? 당연히 Named Parameter 방법을 아셨으니깐 상황이 오신다면 시그니처를 변경하셔서 개발상에 전혀 문제 없이 즐거운 프로그래밍을 할수 있습니다.
그래도 너무 많이 남용하신다면 가독성이 떨어지기 때문에 필요한 시점에서만 사용하시기를 권장합니다.
와우!! 신기하지 않으신가요? 저 감탄까지 했어요 ^^
좀더 이해를 돕기 위해서 예제코드를 준비 해보았는데요.. 어떻게 사용하는지를 보시죠.
[코드 3] Optional and Named Parameters
{
class Program
{
static void Main(string[] args)
{
People("정은성", 27, "남자");
People("정은성", 27);
People("정은성");
People("정은성", Sex:"남자", Age:27);
}
private static void People(string Name, int Age = 27, string Sex = "남자")
{
Console.WriteLine(string.Format("이름은? {0}, 나이는? {1}, 성별은?{2}", Name, Age, Sex));
}
}
}
People메소드를 또 호출하려고 하는데. 코드를 작성하려고 하니깐 인첼리스트 기능에 메타정보가 나타나게 됩니다.
흔히 생략을 할 때 쓰는 중괄호가 보이는데요. 이제부터는 이 매개변수는 생략이 가능하고 생략을 한다면 묵시적으로 데이터가 전달 됩니다. 위 예제 코드는 매개변수를 옵셔널하게 정의를 해주며 생략이 가능한 모습을 보여드리기 위해서 짧게 다루어보았는데요. 다음 포스팅(Com-specific interop features)에서 혼합된 예제를 보여드리도록 하며 이번 포스팅을 마치도록 하겠습니다.
[소스 참고]Hello .NET Framework 4 세미나[세션3] 발표자료와 소스 : http://blog.tobegin.net/35
참고 문헌
1. PDC2008 : The Future of C# - Anders Hejlsberg
: http://channel9.msdn.com/pdc2008/TL16/
2. New Features in C# 4.0 - Mads Torgersen, C# Language PM, April 2010
: http://msdn.microsoft.com/ko-kr/vcsharp/ff628440(en-us).aspx
3. 명명된 인수와 선택적 인수(C# 프로그래밍 가이드)
: http://msdn.microsoft.com/ko-kr/library/dd264739.aspx
포스팅을 마치며...
명명된 인수와 선택적 인수(Optional and Named Parameters) 포스팅 어떠셨나요?
이 기능 덕분에 코드가 심플해졌고 시그니처에 구해받지 않고 명명 할당이 가능해져서 기쁘네요 : )
다음회차에서는 COM 상호운용성(Com-specific interop features)에 대해서 다뤄볼 예정이며
COM 상호운용성 개선 사항 또한 오피스 프로그래밍을 자주 다뤄보신 분이라면 강력하다 라는 말을 하실겁니다.
더 궁금하신점이 있으시면 댓글 혹은 http://blog.tobegin.net/notice/1 프로필정보를 통해 문의 바랍니다.
밤낮 기온이 점점 심해지고 있는데요 건강에 유념하시길 바라며 좋은 하루 되세요 : )
감사합니다.
정은성 드림
[C# 4.0] New Features in C# : 04. dynamic 유형 객체 #2 : Dynamic Lookup
04| Dynamic 유형 객체 #2 : Dynamic lookup
말 못할 일상생활에 빠져 포스팅이 늦어버렸네요. 흑흑..어쨌든 프로젝트 투입전까지 포스팅을 마무리 짓겠습니다.
오늘 다뤄볼 내용은 바로 Dynamic lookup인데요. Dynamic lookup과 함께 C# 4.0에서 가장 큰 변화라고 하면 바로 Dynamic 키워드 도입이라고 할수 있습니다. 이번 포스팅에서는 Dynamic이 어떤 의미를 가지고 있고 Dynamic lookup을 통해 duck Typing의 프로그래밍 스타일에 대해 알아보도록 하는 시간을 가져보도록 하겠습니다.
Dynamic에 대해서 생각을 해보시면 무언가 굉장히 추상적이다 라는 의미를 알 수 있습니다.
C# 4.0의 PM인 Mads Torgersen이 기술한 New Features in C# 내용을 인용한다면 다음과 정의 할 수 있습니다.
- Dynamic Operations
- Runtime Lookup
가. COM objects(COM IDispatch)
나. Dynamic Objects
(IDynamicObject, IronPython, IronRuby, HTML DOM)
- Plain object(.NET object reflection etc)
위 표를 보시면 동적 유형, 동적 연산, 런타입 룩업, 일반 개체로 나누어져 있습니다.
이번 포스팅에서는 전부 살펴볼수 없기에 그 중동적 유형, 동적 연산, 런타임 룩업에 대해서 살펴보도록 하겠습니다.
* 동적 유형(Dynamic Type)
C# 4.0에서 새로운 정적 타입의 동적 키워드를 소개를 하고 있습니다. 동적 유형의 오브젝트를 사용할 때는 런타임 에서만 그 타입을 결정 할 수 있는데요. 먼저 코드를 살펴보도록 하겠습니다.
[코드 1] Dynamic Type Sample
d.M(7);
겉모습으로 볼때 반환하는 메소드 형태와 큰 차이는 없어 보입니다. 그러나 이미 여기서 코드를 보시면서 의문을 가지신분들이 계실것 같은데요. 바로 dynamic 키워드와 GetDynamicObject 메소드의 관계가 굉장히 의심 스럽습니다.
GetDynamicObject 이름을 가진 메소드는 어떤 타입을 가지고 반환하는지 알수가 없을 뿐만 아니라 dynamic이라는 키워드의 변수에 할당하는 모습이 예사롭지 않다는 사실을 알수 있습니다.
C# 4.0 컴파일에서는 GetDynamicObject 메소드와 같이 어떤 이름과 어떤 인수를 가진 메소드든 간에 동적 타입에게 할당할 경우 허용을 하고 있습니다. 이유는 dynamic 형식은 정적 형식이지만 정적형식 검사에서 제외됩니다.
이형식의 타입을 사용하는 개체에서 값을 가져오는 소스가 COM API, Ironpython과 같은 동적언어, DOM 개체모델 등 프로그램 내부인지 다른 위치인지 또는 리플렉션이라도 신경 쓰지 않으셔도 됩니다.
개체 d는 어떤 메소드를 호출 하려고 액션을 취하고 있습니다.
그래도 이해가 되지 않으셨다면 간단한 예제 코드를 살펴보시면 이해가 될것 같네요.
[코드 2] Dynamic Type Sample
int i = d;
위 코드는 [코드 1]의 d.M(7); 코드 구문을 이해 하고자 간단한 코드를 보였는데요.
dynamic d = 7 수식 같은경우 RValue인 숫자 심볼 7을 LValue인 Dynamic d 변수에 할당하는 모습입니다.
이는 컴파일 타임에서 암시적으로 int형 타입으로 캐스팅하는 행위로 재해석할수 있습니다.
int i = d 수식은 위 수식과 달리 런타임에서 동적타입인 d 변수에 대해서 암시적으로 int형으로 캐스팅이 되어집니다.
* 동적 조작(Dynamic Operations)
Method 호출 뿐만 아니라, Field 접근, Indexer 및 Operator 호출, Constructor 호출, 대리자 형식까지 동적으로 재정의할 수 있습니다.다음 코드를 보시면 동적 조작에 대한 예제에 대해 알수 있습니다.
[코드 3] Dynamic Operations Sample
d.M(7); // Method 호출의 예
d.f = d.P; // 해당 Field/Property에 대해 Getter/Setter의 예
d["one"] = d["two"]; // Indexer의 예
int i = d + 3; // Operator 호출의 예
string s = d(5,7); // Delegate를 통해 호출의 예
var c = new C(d); // Constructor 호출의 예
C# 컴파일러의 역할은 단순히 "무엇을 동적"으로 수행되는지에 대해 필요한 정보를 패키지할 뿐만 아니라 런타임에서 그정보가 무엇의 정확한 의미를 가지는지 실제 개체를 동적으로 부여됩니다.
* Dynamic 뭐가 좋아?
Calculator calc = new Calculator();
int sum = calc.Add(10, 20);
[코드 4-2] 컴파일 타입 유추
var calc2 = new Calculator();
int sum2 = calc2.Add(10, 20);
[코드 4-3] 런타임 타입 유추
dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);
첫번째 코드[코드 4-1]는 가장 일반적인 정적 타입 유추 코드이며 두번째[코드 4-2] var 키워드의 특징을 이용하여 존재하지 않지만 컴파일 타임에서 Calculator라는 클래스임을 판단하여 타입을 유추를 하게 되고 해당 두 숫자를 합하여 sum varable에 값을 반환을 하게 됩니다. 세번째 코드[코드 4-3]에서는 dynamic 타입을 사용하였는데요 dynamic 타입은 실행시간을 지연하여 런타임에서 타입을 유추를 하게 됩니다. 처음 Dynamic 에 대해서 설명을 드렸듯이 정적타입 검사에서 Add라는 메서드는 제외가 되므로 런타임에서 바인딩 되어 동적으로 호출합니다.
여기서 만약에 dynamic 키워드가 없다면 어땠을까요?
dynamic 키워드가 없었다면 다음과 같이 작성을 해야합니다.
[코드 5] Reflection Sample
Type calcType = calc.GetType();
object res = calcType.InvokeMember("Add", BindingFlags.InvokeMethod,
null, new object[] { 10, 20 }); int sum = Convert.ToInt32(res);
GetCalculator 클래스가 다른 외부의 객체 모델(Object Model) 은 아니지만 외부 객체라고 가정을 해보겠습니다.
근데 성능상 좋지는 않아보입니다. 왜냐하면 Variant를 위해 불필요하게 object 형식을 다시 해당하는 타입으로 cast을 하고 있는데요. 이는 오버헤드를 발생하게됩니다. 더구나 코드도 어렵네요.
여러분이 객관적으로 보실 때 선택을 한다면 reflection을 사용하시겠습니까?
아니면 dynamic 키워드를 사용하시겠습니까?
이번 포스팅에서 두 특징에 대해서 상세히 다를 순 없지만 간단히 설명을 드리자면 속도면에서 사실상 리플렉션이 더 빠른 결과를 나타내고 있습니다. 그리고 dynamic은 리플렉션에 비해 느리지만 리플렉션의 단점이였던 많은 메모리 소모와 호환성 문제를 높은 수준으로 보장하고 있습니다
여러분들의 코드에 동적으로 무언가를 꼭 해야 한다면 필요한 시점에서 사용하시기를 권장합니다.
* Duck Typing(dynamic lookup)
Dynamic 키워드를 도입을 하면서 dynamic lookup이라는 말과 함께 새로운 동적 프로그래밍 스타일이 이슈가 되었는데요
바로 Duck Typing 이라는 프로그래밍 스타일이 C#에서 가능하게 되었습니다.
위키디피아에서 다음과 같이 정의가 되어 있습니다.
위 문장을 인용을 한다면 오리처럼 행동하면 오리라 부를 수 있다는 의미로써,
문자열처럼 다루게 된다면 그것이 객체이든 뭐든 상관하지 않고 그객체를 문자열처럼
취급해도 된다라는 이야기입니다.
흔히 알고 있는 상속이나 구현과 같이 해당 객체의 부모나, 인터페이스의 영향아닌 그 오브젝트가 가진 특징에 의해 역활을 결정 되는거죠.
즉, Duck Typing과 같이 룩업을 하게 된다면 어떤 클래스나 인터페이스 타입의 오브젝트인지에 상관없이 호출시 해당 오브젝트 존재하기만하면 사용 할 수 있습니다.
* Dynamic Lookup과 함께 Duck Typing 스타일이 이슈가 된 이유?
어떤 어플리케이션을 만들고 테스트를 해야할 때 일반적으로 mock오브젝트를 만들고 테스트를 하죠? 자바와 같은 Static Typing 언어에서는 mock object를 만들기 위해서 실제 사용할 오브젝트와 동일한 인터페이스를 구현을 해야 합니다. 다형성을 활용하여 구현을 할 수 있지만 만약 인터페이스 내에 메소드가 많다면 테스트와 관계없는 메소드까지 구현을 해야 한다는 단점이 있습니다. 다행히 IDE의 도움으로 쉽게 생성을 할 수 있지만 한 메소드를 테스트하기 위해서 모든 메소드를 구현을 해야한다면 좋지 않겠죠? 정말 비효율적인데요. 이런 바탕으로 프로그래밍 스타일이 이슈가 되게 되었습니다.
다음 데모를 보시면서 " Dynamic Lookup이 이렇게 사용하는구나! " 라고 이해가 될것 같네요.
[코드 6] Dynamic Lookup Sample
PeopleInfo(people);
public static void PeopleInfo(dynamic d)
{
Console.WriteLine( "이름: {0}, 나이: {1}" , d.Name, d.Age);
}
코드 6번과 동일한 퍼포먼스를 수행하기 위해서는 리플렉션을 이용하거나 해당 매개변수와 동일한 인터페이스를 가지거나 멤버변수를 지정을 해줘야만 PeopleInfo 메소드를 수행을 할수 있었습니다. Dynamic 도입으로 인해 늦은 바인딩이 가능한 모습을 볼수 있습니다. 다음 데모를 통해 Duck Typing에 대해서 더 많은 이해를 할 수 있습니다.
[코드 7] ExpandoObject Sample
static void Main(string[] args) {
dynamic people = new ExpandoObject();
people.Name = "정은성";
people.Age = new Sameple(); //new Sameple(); or 27
people.Run = new Action(() => Console.WriteLine("Run 메소드 런타임에서 실행 "));
printInfo(people);
people.Run();
}
static void printInfo(dynamic info) {
Console.WriteLine(string.Format("이름{0}, 나이는 {1} 입니다.", info.Name, info.Age)); }
}
class Sameple {
private int Age = 27;
public override string ToString() {
return this.Age.ToString(); }
}
다음 코드 좀 주목해야할 부분입니다.
일반적인 코드라면 동일한 타입이 아닌 이상 오류가 날수 밖에 없는데요.
C# 4.0에서는 Dynamic Lookup 가능하기 때문에 런타임에서 수행가능한 코드라면 그것이
숫자든 문자열든 관계없이 수행을 할수 있습니다.
people.Run = new Action(() => Console.WriteLine("Run 메소드 런타임에서 실행 "));
람다 문장을 활용하여 해당 메소드를 룩업한 모습을 볼수 있습니다.
[코드 8] IronPython Sample ( ※ DevDays2010에서 선보였던 예제코드를 재구성 )
dynamic python = py.UseFile("Calculator.py");
dynamic calc = python.GetCalculator();
for (int i = 0; i < 10; i++)
{
var sum = calc.Add(DateTime.Today, TimeSpan.FromDays(i));
Console.WriteLine(sum);
}
[소스 참고]Hello .NET Framework 4 세미나[세션3] 발표자료와 소스 : http://blog.tobegin.net/35
참고 문헌
1. PDC2008 : The Future of C# - Anders Hejlsberg
: http://channel9.msdn.com/pdc2008/TL16/
2. dynamic(C# 참조)
: http://msdn.microsoft.com/ko-kr/library/dd264741.aspx
3. The Dynamic Keyword in C# 4.0 - CodeProject
: http://www.codeproject.com/KB/cs/TheDynamicKeyword.aspx
4. BOO -Duck Typing
: http://boo.codehaus.org/Duck%2BTyping
포스팅을 마치며...
맨날 바쁘다며 포스팅을 게으르게 올리고 있는데요. 열심히 하겠습니다. 흑흑..
Dynamic Lookup에 관한 포스팅은 여기까지 하도록 하겠습니다.
더 궁금하신점이 있으시면 댓글 혹은 http://blog.tobegin.net/notice/1 프로필정보를 통해 문의 바랍니다.
다음 회차에서는 Optional and Named Parmeters에 대해서 살펴보도록 하겠습니다.
무더위 준비 잘하시고 좋은 하루 되세요 : )
감사합니다.
정은성 드림
[C# 4.0] New Features in C# : 03. dynamic 유형 객체 #1 : DLR
출처 : http://blog.tobegin.net/31 / Writer by 정은성
03| Dynamic 유형 객체 #1 : DLR
구독자 여러분 안녕하세요 생각보다 포스팅이 많이 늦어졌죠?
최근에 처리할일이 생각보다 많아서 포스팅을 쓸 여력이 되지 않았습니다.
앞으로 열심히 쓰고자 하니깐 용서해주세요 ~ 흑흑...
오늘은 지난 시간에 이어서 C#의 새로운 기능에 대해 소개를 할텐데요.
그 첫번째 시간으로 동적 타입에 대해 소개를 하고자 합니다.
지난 Microsoft는 MIX07과 PDC2008 등을 통해 .NET에서 새로운 수준의 지원을 발표를 하였습니다.
바로 .NET 프레임워크에서 서로 다른 프로그래밍 언어를 광범위한 지원을 하도록 설계가 되어졌는데요.
그배경에 공용언어 런타임(Common Language Runtime)의 Sandbox 보안 모델이 발전되면서 GC(Garbage Collection), JIT(just-in-time)를 더불어 공유 서비스를 제공하면서 각 다른 언어의 라이브러리를 손쉽게 가져다 쓸수 있었기에 정적인 언어와 동적인 언어를 구별하여 사용할수 있었지 않았나 생각이 듭니다.
* Sandbox(샌드 박스)
어느듯 .NET Framework 4 버전의 런칭이 되면서 공용 언어 런타임(Common Language Runtime)이 2.0에서 4.0으로 업그레이드가 되었는데요. 그동안 Silverlight 1.1에서 지원을 했던 DLR(Dynamic Language Runtime)이 다시 주목을 받으면서 C# 4.0은 완전한 프로그래밍으로 초점을 맞추어 나가고 있습니다.
먼저 간단히 DLR(Dynamic Language Runtime)에 대해서 살펴 보아야 할것 같습니다.
Dynamic Language Runtime은 CLR 상단부에 위치하고 있으며 주요 구성은 다음과 같습니다.
- Expression Trees
: 트리를 사용하여 구조적 표현의 의미를 노드로 나타냅니다. 다른 언어의 모델링을 지원하기 위해 확장된 LINQ 표현식 트리를 포함하여 동적인 변수 연산이나 변수, 이름 바인딩과 제어흐름 등이 추가 되어 DLR의 중추 역활을 담당합니다.
표현 트리식에 대해서 궁금하시면 아래 URL을 참고 해주세요
표현 트리식 : http://msdn.microsoft.com/ko-kr/library/bb397951.aspx
- Dynamic Dispatch
: DLR에서는 동적 개체 및 작업을 나타내는 인련의 클래스와 인터페이스를 제공합니다. 언어를 구현하거나 동적 라이브러리를 작성할 때 제공되는 클래스와 인터페이스를 사용할 수 있습니다. 해당하는 클래스와 인터페이스로는 IDynamicMetaObjectProvider, DynamicMetaObject, DynamicObject 및 ExpandoObject가 있습니다.
- Call Site Caching
: 런타임 환경에서 실행되어질 동적 개체 대해서 코드에 대한 정보를 은닉해둠으로써 이후 동일 액션의 호출시 수행속도를 향상하는 역활을 합니다. 예를 들어 a + b와 같은 연산이 있을때 a, b의 특징과 작업 관련된 정보를 캐싱해두었다가. 만약 어떠한 작업을 수행해야할때 이전에 그와 같은 작업을 수행한적이 있다면 DLR에서 캐싱된 내용을 디스패치 하여 속도를 향상시킵니다.
위와 같은 구성원이 서로 협력을 통해 동적 프로그래밍이 각 바인더별로 실행이 가능하도록 지원을 하고 있습니다.
DLR에 대해서는 여기까지 살펴보도록 하고 Dynamic lookup에 대해서 살펴보도록 하겠습니다.
혹 자세한 설명이 필요 하시다면 First Look C# 4.0 whiepaper 를 참고하시거나 별도 포스팅을 기획할 예정입니다
참고 문헌
1. 식 트리(C# 및 Visual Basic)
: http://msdn.microsoft.com/ko-kr/library/bb397951.aspx
2. PDC2008 : The Future of C# - Anders Hejlsberg
: http://channel9.msdn.com/pdc2008/TL16/
3. A Dynamic Language Runtime (DLR) - Jim Hugunin's Thinking Dynamic
: http://blogs.msdn.com/b/hugunin/archive/2007/04/30/a-dynamic-language-runtime-dlr.aspx
4. DLR Trees (Part 1) - Jim Hugunin's Thinking Dynamic
: http://blogs.msdn.com/b/hugunin/archive/2007/05/15/dlr-trees-part-1.aspx
포스팅을 마치며...
갑자기 일이 많아져서 포스팅이 늦어졌는데요. dynamic lookup을 살펴보기전에 DLR 존재의 여부에 대해 전달 할 필요가 있는것같아 간소화하게 작성하게 되었는데 왠지 모를 어색하고 주절주절 한 포스팅 이 되어버렸네요 T.T
다음 회차에서는 dynamic lookup의 특징과 C#의 새로운 프로그래밍 스타일인 duck typing 에 대해 살펴볼 예정입니다.
무더위 준비 잘하시고 좋은 하루 되세요 : )
감사합니다.
정은성 드림
출처 : http://blog.tobegin.net/31 / Writer by 정은성
[C# 4.0] New Features in C# : 02. C# 개요 및 배경
출처 : http://blog.tobegin.net/29 / Writer by 정은성
02| C# 4.0 개요
이번 포스팅은 .NET Framework 4의 " C# 새로운 기능 소개와 출현배경" 에 대해 아티클로 다뤄보도록 하겠습니다.
그동안 2.0 버전에서 머물렀던 CLR(Common Language Runtime)이 .NET Framework 함께 4.0 버전으로 업그레이드가 되었습니다. CLR의 가장 큰 변화로는 DLR(Dynamic Language Runtime)을 통해 언어의 유연성이 느슨하게 되면서 DOM, IronRuby, IronPython 등 동적인 언어를 사용 할수 있도록 설계 되어졌습니다.
지금까지 Visaul C#언어는 정적 언어 였습니다. 정적 언어라고 하면 명확하게 타입 선언과 사용이 매칭이 되어야합니다. 만약 숫자를 사용하고 싶다면 int형 타입으로 선언을 해야하며 그리고 문자열 사용하고 싶다면 string형 타입으로 선언과 타입을 넘겨줘야합니다. 그런데 왜 이제서야 동적 언어를 지원을 하게 되었을까요? C# 디자인팀에서 4.0을 설계 할때 2가지 컨셉중심으로 준비 하였다고 합니다.
첫번째 컨셉은 Dynamic Programming 인데요
Dynamic Programming은 4.0에서 등장 했던 것은 아닙니다. invokeMember와 같이 Reflection을 이용하여 동적으로 사용이 가능했습니다. 하지만 메모리 소모량도 크고 불안정하기 까지 하였습니다. 그래서 C# 4.0에서는 DLR(COM Interop 포함)에 대한 높은 호환성과 Iron 시리즈의 스크립팅 언어를 비롯해 늦은 바인딩 언어를 지원하고자 dynamic 키워드를 도입하게 되었고 유연성을 허용 하게 되었습니다.
두번째 컨셉으로는 co-evolution with VB 입니다.
지난 10년간 .NET Framework가 발전을 하면서 VB, C#, C++, IronRuby, IronPython, JScript , J# 등 많은 언어들을 지원하며 Managed 하게 진화해 왔습니다. 그중 마치 형제인듯 공존하며 성장을 해왔던 언어가 있는데요.
바로 VB와 C#언어 입니다. 마침 MS OFFICE 프로그램의 오토메이션 사례등 유사한점을 확인 할 수 있었습니다. 예를들어 어떠한 데이터를 셀에 삽입한다고 가정할 때 C#에서는 Value2라는 속성을 사용하였고 반면 VB에서는 Value라는 속성을 사용하였습니다. 이들의 특징은 동일한 내용의 타입 이였고 동일한 시나리오의 수행이였습니다. 그럼 구현의 퍼포먼스에서는 어땠을까요? VB에서는 사용이 쉬운 반면 C#에서는 전혀 알지도 못한 타입 함께 사용하게 되었습니다. 자연스럽게 코드가 복잡해지고 어려울수 밖에 없었죠. 이러한 기존 VB의 특징을 바탕으로 어떻게 하면 효율적으로 사용할것인지 또한 어떻게 COM객체와 상호연동을 할것인지? 등 함께 성장하자라는 취지로 C# 4.0이 탄생하게 되었습니다.
다음은 두 컨셉의 기반으로 탄생한 C# 4.0의 새롭게 추가된 구성원이 무엇인지 간단히 살펴보도록 하겠습니다.
* C# 4.0의 새로운 기능들
- Optional and Named Parmeters
- Com-specific interop features
- Generic Co-Variance and Contra-Variance
명칭만으로도 궁금증을 돋아 주는데요. 아쉽게도 이번 포스팅에서는 헤드라인 만 살펴봐야할것 같습니다.
다음 포스팅부터 새롭게 추가된 기능에 대해 순차적으로 만나뵐 예정이구요.
궁금하셔도 조금만 기다려주세요 : )
그래도 궁금하시다면 아래 참고 문헌을 참조하시면 됩니다.
여러분 다음 포스팅에 만나요 =ㅁ=//
참고 문헌
1. New Features in C# 4.0 - Mads Torgersen, C# Language PM(April 2010)
: http://msdn.microsoft.com/en-us/vcsharp/ff628440(en-us).aspx
2. What's New in Visual C# 2010
: http://msdn.microsoft.com/kr-ko/library/bb383815.aspx
3. PDC2008 : The Future of C# - Anders Hejlsberg
: http://channel9.msdn.com/pdc2008/TL16/
포스팅을 마치며...
어떤 느낌을 받으셨나요? 아직 무엇인지는 알 수 없지만 의미적으로 많은 지원을 제공하고자 Microsoft에서 야심차게 준비하는 모습을 볼 수 있었습니다. 다음 포스팅 에서는 새로운 기능의 특징들을 순차적으로 살펴 보도록 하겠습니다.
앞으로도 관심 부탁드리며 무더위 준비 잘하시길 바라면서 글 이만 줄이도록 하겠습니다.
감사합니다.
정은성 드림
출처 : http://blog.tobegin.net/29 / Writer by 정은성
[C# 4.0] New Features in C# : 01. C# 프로그래밍 동향
출처 : http://blog.tobegin.net/39 / Writer by 정은성
01| C# 프로그래밍 동향
어느듯 .NET Framework 4가 Visual Studio 2010과 함께 런칭한지 3개월이 흘렀습니다.
Visual Studio 2010 과 함께 .NET Framework 4, Silverlight 4, WPF, WCF 등 유관 기술들이 새로운 변화를 하고 있는데요.
그래서 오늘은 Microsoft에서 지향하는 프로그래밍 패러다임에 대해 아티클로 잠시 다루어보기로 하겠습니다.
.NET Framework 디자인팀은 오래전부터 " 미래에는 어떤형태의 프레임워크가 되어야할지? " 고민을 하였다고 합니다.
지속된 디자인팀의 노력으로 .NET Framework 4에서는 완전한 프로그래밍으로 거듭 나고 있습니다.
* 선언적 프로그래밍
: 명령형 프로그래밍의 반대되는 개념으로 어떤 방법으로 해야 하는지를 나타내기보다 무엇과 같은지를 설명하는 경우를 말합니다. 그내용을 축약한다면 목표를 명시하고 알고리즘은 명시하지 않습니다.
즉, 찾고자하는 필요한 해의 특성을 설명하고 그해를 찾는데 사용하는 실제 알고리즘은 설명을 하지 않습니다.
대표적인 예로는 SQL과 같이 질의문의 선형 언어가 있습니다. 이미 C# 3.0에서 선언형 언어 이론을 기반한 LINQ와 간결하고 확장 가능한 구문 람다 문장과 같은 스타일이 적용 된것을 알수 있습니다.
* 동적 프로그래밍
: 위키 사전에서는 다음과 같이 정의를 하고 있네요.
"어떤 알고리즘이 부분 문제 반복과 최적 기본 구조라는 특징을 가지고 있을 때, 이 알고리즘의 실행시간을 줄이는 방법이다."
이와 같은 이론을 바탕으로 C# 4.0 에서는 Dynamic 이라는 새 형식이 도입이 되었습니다. dynamic 형식은 정적 형식이지만 이형식의 개체는 정적 형식 검사에서 제외가 됩니다. 또한 dynamic 타입은 컴파일 타임에 모든 작업을 지원하는 것으로 가정되며 COM API, IronPython, IronRuby 와 같은 언어이 프로그램 내의 다른 위치인지 신경 쓰지 않아도 됩니다.
CLR 버전이 4.0으로 업그레이드 되면서 동적 언어 런타임(DLR)을 통해 동적인 언어를 지원 합니다.
* 동시 프로그래밍
: 동시 프로그래밍은 동시에 많은 연산을 하는 방법중 하나입니다. 최근 컴퓨터 이용에서 발열과 전력소모에 대한 관심이 높아지는것과 함께, 멀티 코어 프로세서를 핵심으로 컴퓨터 구조에서 강력한 패러다임으로 주목받고 있습니다.
.NET Framework 4에서는 병렬 컴퓨팅의 패러다임을 손쉽게 가져다 쓸수 있도록 제공을 하고 있으며 .NET 4의 병렬 프로그래밍은 Task Parallel Library(TPL)과 PLINQ (Parallel LINQ)으로 구분 됩니다.
- TPL : 데이터 중심으로 네트워크 통신 및 병렬 연산 등을 통하여 실행 순차적으로 배포 또는 인터리빙이 실행하여 단일 프로세스들을 집합 과정으로 기존의 전통적인 비동기 패턴과 TPL을 통합하는 시나리오를 제공합니다.
- PLINQ : LINQ는 안정적인 방식으로 데이터 소스를 쿼리하기 위한 모델였지만 데이터가 열거 되기전까지 지연이 되어 병렬적으로 실행이 필요 하게 되었습니다. PINQ의 병렬 처리로 레거시 코드를 사용할 때보다 향상된 성능을 제공합니다.
포스팅을 마치며...
이번 포스팅은 Microsoft에서 추구하는 프로그래밍 패러다임에 대해서 잠시 아티클로 만나 보았습니다.
의미적으로 잘못 전달해드리지 않을까 걱정되네요 ^^; 추가 피드백이 있으시면 의견 남겨주시면 감사합니다.
다음 포스팅 부터는 C#의 새로운 기능들에 대해 소개를 해드릴 예정입니다.
무더위 준비 잘하시고 항상 좋은 하루 보내세요
감사합니다.
정은성 드림
C#2.0 에 추가된 자료형 Generics
Microsoft는 “Visual Studio for Yukon” Version의 C# 언어로 여러 가지 개발 및 산업 언어의 다양한 기능을 통합 하려는 계획을 세우고 있습니다. 이 계획의 일부로 개발자의 생산성을 향상시켜 주는 실용적인 언어 구문으로 Generics, Iterator, Anonymous, Partial 등의 기능이 포함 되었습니다.
여기서 간단히 다룰 자료형은 Generics입니다. Generic을 우리말로 ‘일반적, 포괄적이다’ 라고 번역할 수 있습니다. 우리는 앞에서 자료형은 값 형식(Value Type)과 참조 형식(Reference Type)으로 구분 되어지고, 값 형식(Value Type)은 Stack에 참조 형식(Reference Type)은 Heap에 저장된다고 배웠습니다. 그러나 이 두 가지 자료형을 모두 고려한 메소드를 만들고자 하면 System.Object 형으로 변환하여 저장하고, 다시 값 형식(Value Type)이나 참조 형식(Reference Type)으로 변환하여 사용해야 하는 불편함 및 많은 문제점을 야기 시켰습니다.
이를 대체하기 위해 미리 Type을 지정하지 않고 실제 자료형이 저장되는 시점에 자료형이 결정 되어지는 일반적인 자료형을 지원하기 위해 Generics 자료형이 추가된 것입니다.
1. Generics의 필요성 제시(System.Object 사용에 따른 문제 정의)
예를 들어 범용 Stack 데이터 구조를 개발한다고 가정해봅시다. Stack의 지원 메서드로는 Pop() 및 Push() 메서드가 있습니다. 이를 사용하여 다양한 형식의 인스턴스를 저장할 수 있습니다. 즉, Stack에 사용되는 내부 데이터 형식은 무정형 개체이며, stack 메서드는 개체와 상호 작용합니다.
.Net 기반 형식으로 기본 제공되는 개체 기반 stack을 사용하여 모든 형식의 항목을 보관할 수 있습니다. 그러나 개체 기반 솔루션에서 다음과 같은 문제점이 있습니다.
public class Stack { readonly int m_Size; int m_StackPointer = 0; object[] m_Items; public Stack():this(100) {} public Stack(int size) { m_Size = size; m_Items = new object[m_Size]; } public void Push(object item) { if(m_StackPointer >= m_Size) throw new StackOverflowException(); m_Items[m_StackPointer] = item; m_StackPointer++; } public object Pop() { m_StackPointer--; if(m_StackPointer >= 0) return m_Items[m_StackPointer]; else { m_StackPointer = 0; throw new InvalidOperationException("스택이 비워 있어 Pop할 자료가 없습니다."); } } } Stack vstack = new Stack(); vstack.Push(1); int number = (int) vstack.Pop(); Stack rstack = new Stack(); rstack.Push(new Customer()); Customer c = (Customer) rstack.Pop(); |
<문제점 제시>
1) 성능에 문제가 있습니다.
값 형식(Value Type)을 사용할 경우 값을 넣으려면(Push) 박싱(Boxing) 처리하고, 받아 내려면(Pop) 언박싱(UnBoxing) 처리를 해야 하는데 그 자체만으로도 성능이 크게 저하되며, 의 부담도 늘어 나므로 성능이 좋지 않습니다.
참조 형식(Reference Type)을 사용하는 경우에도 개체에서 상호 작용하고 있는 실제 형식으로 형 변환 해야 하고 형 변환에 따른 추가 작업이 필요하므로 성능이 여전히 저하됩니다.
Stack vstack = new Stack(); vstack.Push(1); int number = (int) vstack.Pop(); |
2) 형식의 안전성에 문제가 있습니다.
컴파일러(Compiler)에서는 모든 내용을 Object 타입으로 형 변환 하거나, Object 타입에서 다른 타입으로 형 변환할 수 있으므로 컴파일(Compile)시 형식의 안전성이 떨어 집니다.
타입별로 안전한 스택을 만들어 박싱 및 형 변환에 따른 성능 저하 및 컴파일(Compile)시 잘못된 형 변환에 따른 형식의 안전성 문제를 해결할 수 있을 것입니다. 하지만, 이에 못지 않은 문제점이 발생 합니다.
Stack rstack = new Stack(); rstack.Push(new Customer()); Customer c = (Customer) rstack.Pop(); |
3) 작업 생산성이 낮아지는 상황이 발생합니다.
타입별 데이터 구조를 작성하는 작업은 더디고, 반복적이며 오류가 발생하기 쉽습니다. 데이터 구조에서 결함을 수정할 경우 동일한 데이터 구조를 타입별로 중복 항목이 있는 곳마다 결함을 해결해야 합니다. 또한 알 수 없거나 아직 정의되지 않은 미래의 형식이 사용될지도 모르므로 개체 기반 데이터 구조도 유지해야 합니다.
public class Stack { int[] m_Items; public void Push(int item) {...} public int Pop() {...} } |
2. Generics의 정의
C# Generics는 새로운 주요 개발 방법으로서 적절하고 읽기 쉬운 구분을 사용하여 성능, 형식 안정성 및 품질을 향상 시키고, 반복적인 프로그래밍 작업을 줄이, 전체 프로그래밍 모델을 단순화 하는 새롭게 추가된 자료형입니다. IL(Intermediate Language) 및 CLR(Common Language Runtime) 자체에서 기본적으로 지원하며, generic Class 코드를 컴파일하면 다른 모든 형식과 마찬 가지로 IL로 컴파일합니다. 단, IL에는 실제 특정 형식의 매개 변수나 자리 표시자만 들어 있습니다. 또한 Generic Class의 메타 데이터에는 Generic 정보만 들어 있습니다. 다음은 C# Generics에 대해 쉽게 표현하고 있습니다.
3. Generics의 구현 (적용)
IL(Intermediate Language) 및 CLR(Common Language Runtime)에서 generics를 기본적으로 지원하므로 CLS(Common Language Specification) 규약 및 CTS(Common Data System) 데이터 형식이 적용된 대부분의 CLR 호환 언어는 generic 형식을 활용할 수 있습니다. 다음은 앞에서 개체 기반 stack을 Generics를 적용하여 C#으로 구현해 보겠습니다.
public class Stack<T> { readonly int m_Size; int m_StackPointer = 0; T[] m_Items; public Stack():this(100) {} public Stack(int size) { m_Size = size; m_Items = new T[m_Size]; } public void Push(T item) { if(m_StackPointer >= m_Size) throw new StackOverflowException(); m_Items[m_StackPointer] = item; m_StackPointer++; } public T Pop() { m_StackPointer--; if(m_StackPointer >= 0) { return m_Items[m_StackPointer]; } else { m_StackPointer = 0; throw new InvalidOperationException("스택이 비워 있어 Pop할 자료가 없습니다."); } } } Stack<int> vstack = new Stack<int>(); vstack.Push(1); int number = vstack.Pop(); Stack<Customer> rstack = new Stack<Customer>(); rstack.Push(new Customer()); Customer c = rstack.Pop(); |
Stack 클래스 선언 시 클래스명 뒤에 꺾쇠 괄호 안에 Generic 형식 매개 변수를 지정하여 Stack Generic 클래스 선언합니다.
public class Stack<T> |
Object 간에 데이터 변환하는 대신 Generic 클래스의 Instance는 해당 Instance가 만들어진 데이터 형식을 받아 들이며, 해당 형식의 데이터를 변환 없이 저장합니다. Generic 형식 매개 변수 T는 지정된 실제 형식이 사용될 때까지 자리 표시자 역할을 합니다. T는 내부 항목 배열의 요소 형식(T[] m_Items;)으로, Push 메서드에 대한 매개 변수 형식(public void Push(T item))으로, Pop 메서드에 대한 반환 형식(public T Pop())으로 사용됩니다.
Generic Class의 Instance 생성시 꺾쇠 괄호 표기법을 사용하여 기본 정수 형식을 매개 변수로 지정하여 Stack Class에서 정수 형식을 사용하도록 합니다.
Stack<int> vstack = new Stack<int>(); vstack.Push(1); int number = vstack.Pop(); |
Customer Class(Reference Type)를 매개 변수로 지정하여 Stack Class에서 Customer Class 형식을 사용하도록 합니다.
Stack<Customer> rstack = new Stack<Customer>(); rstack.Push(new Customer()); Customer c = rstack.Pop(); |
Generic 형식 매개 변수는 형식의 기본값을 반환하는 default라는 속성을 지원합니다.
Stack의 Pop() 메서드를 다음과 같이 수정하여 Exception으로 처리하지 않고 default 값을 Return하도록 합니다(참조 형식(Reference Type)의 기본값은 null이고, 정수 또는 열거 및 구조체와 같은 값 형식(Value Type)일 경우 기본값은 ‘0’입니다).
public T Pop() { m_StackPointer--; if(m_StackPointer >= 0) { return m_Items[m_StackPointer]; } else { m_StackPointer = 0; return T.default; //throw new InvalidOperationException("스택이 비워 있어 Pop할 자료가 없습니다."); } } |
위와 같이 클래스에서 Generics을 사용했듯이 구조체에서도 Generics을 사용할 수 있습니다. 예제를 통해 유용한 Generic point 구조체를 만들어 봅시다.
001 using System; 002 using System.Collections.Generic; 003 using System.Text; 004 005 namespace DataGenericsType 006 { 007 public struct Point<T> 008 { 009 public T X; 010 public T Y; 011 } 012 013 class Program 014 { 015 static void 016 { 017 // 일반정수좌표 018 Point<int> intPoint; 019 intPoint.X = 1; 020 intPoint.Y = 2; 021 Console.WriteLine(" 022 Console.WriteLine(""); 023 024 // 부동소수점 정밀도가 필요한 차트좌표 025 Point<double> doublePoint; 026 doublePoint.X = 1.2; 027 doublePoint.Y = 3.4; 028 Console.WriteLine("부동 소수점 Point 좌표: X({0}), Y좌표({1})", doublePoint.X, doublePoint.Y); 029 Console.WriteLine(""); 030 } 031 } 032 } |
[Generics형 변수 실행결과]
4. C# Generics와 다른 언어에서 지원되는 Generics의 차이점
C++의 Template (Generics 지원)
C++에서는 Template라는 형태로 Generics를 제공해 왔습니다. 패턴적인 측면에서는 C#의 Generics와 크게 다르지 않으나, 결정적인 차이가 있다면 C#에서는 CLR이 지원한다는 것입니다. 일종의 매크로(템플릿에 제공된 각 형식 매개 변수에 대한 특수화된 형식을 생성)일 뿐인 C++의 Template과는 달리 Generics는 Runtime시에 JIT 컴파일러에 의해 적절히 해석되고 Native Code로 컴파일된다는 것입니다.
C++와 같은 후기 바인딩 방식에서는 단지 클래스 또는 인터페이스의 상속을 통한 바인딩이었기에 Logic의 확장이 어려웠고, 형식의 안정성에서의 문제점도 꼼꼼히 따져야만 했습니다. 적어도 전달되는 클래스는 같은 클래스에서 상속을 받거나 같은 인터페이스를 구현해야 했습니다. 물론, COM의 IUnknown 같이 자기기술자[Self-Descriptor]를 가진 클래스/인터페이스를 만들면 해결할 수 있겠지만 그 노력은 만만치 않습니다. C#의 Generics를 적용하면 ‘Logic’과 ‘Logic에 의해 다뤄지는 객체’를 명확하게 구분할 수 있습니다.
결국 Domain/Business Logic을 구현이라는 점에서는 크게 다를 바가 없지만 그 유연성에 있어서는 상당히 선택의 폭이 넓어졌다고 할 수 있다.
JAVA의 Generics (Parametric polymorphism)
Java의 Generics는 Pizza라는 프로젝트로부터 시작되었으며, 이후 GJ라고 이름이 붙여졌고, JSR로 바뀐 것입니다. Sun은 Java Virtual Machine을 수정할 필요가 없는 구현을 선택했습니다. 따라서 Sun은 수정되지 않은 가상 시스템에서 generics를 구현함으로 해서 많은 단점들을 가지게 되었습니다.
Java Generics는 어떤 실행 능률도 높여 주지 못합니다. Java Compiler는 List<Integer>라는 Parameterized Type을 만들면 Integer를 Object로 모두 변환하고 필요한 곳에 (Integer) 형 변환을 실행합니다. 이는 수정되지 Java Virtual Machine이 값 형식(Value Type)에 대해 Generics를 지원하지 못하기 때문입니다. 따라서 Java의 Generics에서는 실행 효율성을 얻을 수 없습니다.
Java의 Generics는 erasure of the type parameter(매개변수 타입의 말소)라는 것에 의존하기 때문에 Runtime시에 List<Integer>는 List라는 클래스와 동일한 클래스로 표현됩니다. 이것은 어떤 클래스가 List<Integer>인지 List<String>인지 Runtime시에는 알 수 없다는 것입니다. 이는 Runtime시에 Generics 형식의 Instance에 대한 형식 매개 변수를 확인할 수 방법이 없으며 다른 리플렉션(Reflection) 사용이 엄격하게 제한된다는 의미입니다.
[출처] C#2.0 에 추가된 자료형 Generics|작성자 독선생
C#의 string 타입과 StringBuilder 비교
3. String과 StringBuilder 비교
string은 프로그램을 짜면서 가장 빈번하게 사용되는 자료형의 하나이다. string을 쓰는데 있어서 흔히들 string 연산이 많을 경우 string 타입 대신에 StringBuilder 객체를 쓰라는 얘기를 들어 보았을 것이다. 여기에서는 string과 StringBuilder의 차이에 대해 다루며 각각을 어떻게 이용하는 것이 효율적인지에 대해 다루어보도록 하겠다.
먼저 아래의 간단한 프로그램을 보도록 하자.
class Program { static void Main(string[] args) { string str1 = "" StringBuilder strBuilder = new StringBuilder();
// string object Console.WriteLine("\nString Object\n"); for (int i = 0; i < 17; i++) { str1 = str1 + "A" Console.WriteLine(GC.GetTotalMemory(true).ToString()); }
// StringBuilder object Console.WriteLine("\nStringBuilder Object\n"); for (int i = 0; i < 17; i++) { strBuilder.Append("A"); Console.WriteLine(GC.GetTotalMemory(true).ToString()); } } } |
위의 예제 소스는 간단히 프로그램의 메모리 사용량을 비교해보기 위해 만들어본 소스이다. 단순히 증가되는 프로그램 영역의 메모리를 카운트하는 용도로 GetTotalMemory 라는 메서드를 이용해서 비교해 보았다. 이제 결과를 보기로 하자.
그림. 실행결과
string
실행결과 그림에서 보이는 바와 같이 string 으로 선언된 str1 변수의 경우 17번 반복하는 동안 연속적인 메모리 증가를 보인다. 실제 string의 내부적인 구현은 char 타입의 연속적인 열의 형태로 구성되는데, 선언시 초기화되는 데이터의 값으로 string 타입 변수의 크기가 결정된다고 보면 된다. 위의 소스와 같이 “+”와 같은 결합 연산자를 이용하여 경우 데이터의 크기가 증가하므로 할당되는 메모리 역시 증가되어야 한다.
이제 코드를 상세히 보도록 하자. 위의 소스에서 다룬 스트링 연산에서,
str1 = str1+"A"; |
부분을 디스어셈블러를 통해 추적해 보면 Concat(string, string)를 호출하는 것을 볼수 있다.
IL_0062: call string [mscorlib]System.String::Concat(string, string) |
실제 Concat 라는 메서드를 한번 보기로 하자.
public static string Concat(string str0, string str1) { if (string.IsNullOrEmpty(str0)) { if (string.IsNullOrEmpty(str1)) { return string.Empty; } return str1; } if (string.IsNullOrEmpty(str1)) { return str0; } int num1 = str0.Length; string text1 = string.FastAllocateString(num1 + str1.Length); string.FillStringChecked(text1, 0, str0); string.FillStringChecked(text1, num1, str1); return text1; } |
소스에서 눈여겨 봐야 할 부분은 FastAllocateString이라는 메서드 이다. 여기에서 보이는 것과 같이 새로 메모리를 할당하는 것을 볼 수 있다. 실제 Concat 메서드를 통해 볼 수 있듯이 새로운 문자열과 결합해서 문자열을 생성해내야 하는 문자열 연산은, 연산이 이루어질때마다 새로운 string 개체를 생성하고 스택 내 메모리를 재할당하는 과정을 반복하게 된다. 문자열에 대해 증가에 대해서 “A"라는 문자를 증가시킬 때 인덱스가 2씩 증가할 때 마다 메모리가 증가되는 이유는 char 형이 실제 2바이트를 이용하지만 할당되는 메모리 단위기 4바이트씩 증가되기 때문에 실행결과 그림과 같이 보인다.
string 타입의 제약은 메모리 재할당 문제로 인해 발생한다. 메모리의 안전한 보호와 동적인 수정이 가능한 구조로 가기 위해 실제 string 타입이 선택한 방법은 수정요구시 마다, 새로운 string 타입을 생성하고 적절한 사이즈로 메모리를 할당하고 문자열을 배정하는 것이라고 보면 된다.
StringBuilder
처음 나온 예제소스에서 보면 17번 반복해서 문자열을 한자씩 더해가고 있다. 그 이유는 StringBuilder 개체를 생성할 때, 초기에 그 크기를 선언하지 않으면 16으로 크기가 자동으로 결정된는 것을 보여주기 위해서 문자열의 크기를 맞추어 보았다. 출력 결과를 보면 “A"라는 문자가 16개가 된 후 StringBuilder 개체 역시 메모리 재 할당 과정을 수행하게 된다. 따라서 테스트용으로 작성된 소스의 17번째 반복 시점에서 메모리 사용이 증가하는 것을 발견할 수 있게 된다.
메모리 재할당이 이루어지는 단계에서는 새로운 StringBuilder 개체 생성과 기존 크기의 2배 크기로 메모리 할당, 기존 데이터의 복사가 이루어진다.
자 이제, StringBuilder 개체 생성시 적절한 사이즈를 설정한 후 작업을 하게 되면 재할당 과정에 드는 리소스의 사용을 최소화 할 수 있다는 것을 알 수 있을 것이다.
StringBuilder에 대한 좀더 상세한 내용은 다음에 더 다루어 보도록 하겠다.
결론
string과 StringBuilder는 비슷한 용도로 활용되면서, 많은 차이점을 가지고 있다. 일반적으로 얼마되지 않는 스트링 연산과 데이터를 이용하는데 있어서 string이 성능상 더 나을 수도 있다. 그럼 StringBuilder을 왜 사용하는 것일까?
아래의 간단한 성능 테스트 코드를 보자.
class Program { static void Main(string[] args) { StringBuilder strBuilder = new StringBuilder(100000); string str1 = ""
// 시간 측정을 위한 코드 Stopwatch stopWatch1 = new Stopwatch(); Stopwatch stopWatch2 = new Stopwatch(); TimeSpan time1, time2;
// string object Console.WriteLine("\nString Object");
stopWatch1.Start(); for (int i = 0; i < 100000; i++) { str1 = str1 + "A" } stopWatch1.Stop();
time1 = stopWatch1.Elapsed; Console.WriteLine("string 반복시간 : " + time1.Milliseconds.ToString());
// StringBuilder object Console.WriteLine("\nStringBuilder Object"); stopWatch2.Start(); for (int i = 0; i < 100000; i++) { strBuilder.Append("A"); } stopWatch2.Stop();
time2 = stopWatch2.Elapsed; Console.WriteLine("StringBuilder 경과 시간 : " + time2.Milliseconds.ToString()); } } |
위의 소스는 시간 측정을 하기 위해 System.Diagonstics 의 Stopwatch 클래스를 이용하는 코드와 결과를 명확히 하기 위해 반복 횟수를 100000으로 조절하여 테스트 한 소스이다. 변수 할당같은 경우 소요시간이 엄청 짧기 때문에 반복횟수를 크게 했다. 결과는 아래와 같다.
그림. 실행결과
동일한 100000번의 반복에서 경과시간을 비교해볼 수 있을 것이다. 이렇듯 많은 연산이 요구되는 경우 string은 성능상에 많은 문제를 일으킬 수 있다는 것을 보여주고 싶었다.
많은 연산이 수행되는 경우 효과적인 성능을 보장할 수 있기에 StringBuilder 클래스를 이용해서 작업을 수행하는 것이다. 적절한 크기를 명시하고, 적절한 장소에서 이용된다면 편리하고 시스템에 덜 부담을 주는 코드를 만들어 낼 수 있게 된다.
적당한 것 예를 들어, 데이터베이스 컨넥션 스트링이나 기타 문자열 결합 형식 등을 통한 일반적인 몇 자 안되는 문자열 생성 등에는 string 타입 변수 선언을 해서 이용하는 것이 이득이고, 대규모의 문자열에서 잦은 수정과 반복을 통한 추가 등이 이루어져야 한다면 StringBuilder을 쓰는게 좋을 수 있다.
리플들
안녕하세요.
글은 잘 읽었습니다. 그리고 궁금한게 있어서요..
만약 StringBuilder의 크기를 주지않고 한번에 더하는 텍스트의 길이가 16이상인 텍스트를 10번 더하는 경우라면, string과 StringBuilder가 같은 성능을 보이게 되는 것인지요?
아~~ 늦게서야 확인했습니다.
요즘은 데브피아에 잘 못들어와서.ㅡ.ㅜ
StringBuilder의 경우, 선언된 크기보다 큰 문자열이 들어올 경우 재할당을 하게되는데 크기의 증가는 배수로 증가하는 로직을 탑니다.^^;
처음에 16, 다음에 32, 64, 128.... 이런식으로요. 경우에 다라서 다르지만 String보다는 여전히 StringBuilder이 유리하죠.^^;
String의 경우 간단하게 보면 10번 개체가 생성되어야 하지만, StringBuilder의 경우 5번 생성된다고 보시면 됩니다.ㅎㅎㅎ
출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1229&page=15
C# 코딩 연습 – 인터페이스의 명시적 구현
인터페이스의 구현에는 두 가지가 있습니다. 이른바 암시적 구현과 명시적 구현이라고 하는데요, 일반적인 경우에는 주로 암시적 구현을 사용하지만 경우에 따라서는 명시적 구현을 사용해야 하는 경우도 있습니다.
먼저 암시적 구현 부터 살펴볼까요? 두 개의 인터페이스가 있다고 합시다.
01 public interface IGunner
02 {
03 void Shoot();<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
04 }
05
06 public interface ISoccerPlayer
07 {
08 void Shoot();
09 }
코드 1
IGunner는 총을 쏘는(shoot)을 메소드를 정의하고 있고, ISoccerPlayer는 공을 차는(shoot) 메서드를 정의하고 있습니다. 공교롭게 메서드의 이름이 같긴 하지만, 이 두 인터페이스는 서로 전혀 연관이 없습니다. 또한 3번과 8번 라인에서 아무런 접근 지정자가 붙어있지 않은 점도 유심히 보시기 바랍니다. 클래스의 경우 멤버의 접근 지정자가 붙어있지 않다면 private이 생략된 것으로 간주하지만, 인터페이스의 경우에는 public이 생략된 것으로 간주합니다. 아니 간주하는 것이 아니라, 인터페이스의 멤버는 그 성격상public외의 접근 지정자를 가질 수가 없습니다. 따라서 항상 public이기 때문에 인터페이스의 멤버에는 아무런 접근 지정자를 붙일 필요가 없습니다. 오히려 접근 지정자를 붙이면 문법 오류입니다.
이번에는 이들 인터페이스를 각각 구현하는 총잡이와 축구 선수 클래스를 생각해봅시다.
01 internal class Gunner : IGunner
02 {
03 public void Shoot()
04 {
05 Console.WriteLine("빵빵");
06 }
07 }
08
09 internal class SoccerPlayer : ISoccerPlayer
10 {
11 public void Shoot()
12 {
13 Console.WriteLine("슛");
14 }
15 }
코드 2
인터페이스에 정의된 메서드의 이름이 같기 때문에 이를 구현하는 두 클래스에서의 메서드 이름도 같을 수 밖에 없지만, 그 동작은 전혀 다릅니다. 총잡이는 총을 쏘고, 축구 선수는 공을 차지요. 그리고 3번 라인과 11번 라인에서는 public 접근 지정자가 붙어 있습니다. 인터페이스를 정의할 때는 붙이지 않았던 접근 지정자를 인터페이스를 구현하는 클래스에서는 이렇게 지정하여야 합니다. 인터페이스에서의 접근 지정자가 (생략된) public 이었기 때문에, 클래스에서도 public 이외의 다른 값을 가질 수는 없습니다. (곧 살펴 볼 명시적 인터페이스에는 적용되지 않는 이야기입니다..)
총잡이와 축구 선수의 인스턴스를 만들고 각각 Shoot 메서드를 호출해보도록 합시다.
1 private static void Main(string[] args)
2 {
3 Gunner gunner = new Gunner();
4 gunner.Shoot();
5
6 SoccerPlayer soccerPlayer = new SoccerPlayer();
7 soccerPlayer.Shoot();
8 }
코드 3
빵빵
슛
계속하려면 아무 키나 누르십시오 . . .
당연히 의도한 대로의 결과가 나왔습니다.
여기까지가 인터페이스의 암시적 구현에 관한 이야기였습니다. 지금부터는 인터페이스의 명시적 구현에 관한 내용입니다.
명시적 구현을 설명하기 위해 새로운 클래스를 하나 등장 시켜야겠습니다. 군인 클래스인데요. 군인이니까 당연히 총을 쏩니다(shoot). 그리고 군인이니까 당연히(?) 공도 잘 찹니다(shoot). 그래서 병사 클래스는 IGunner와 ISoccerPlayer를 모두 상속 구현하도록 결정을 하겠습니다.
01 internal class Soldier : IGunner, ISoccerPlayer
02 {
03 public void Shoot()
04 {
05 Console.WriteLine("빵빵");
06 }
07
08 public void Shoot()
09 {
10 Console.WriteLine("슛");
11 }
12 }
코드 4
한 눈에 봐도 에러입니다. Shoot 메서드가 중복이 되었지요. 위와 아래의 메서드는 각각 Gunner와 SoccerPlayer의 Shoot인데, 이를 구별하지를 못합니다. 즉 시그니처(메서드의 형을 구별하는 기준, 쉽게 말하면 메서드의 이름과 매개 변수의 개수와 각 매개 변수의 형)가 동일한 메서드를 가진 두 인터페이스를 동시에 구현 상속하지 못하는 상황을 해결하기 위해 C#의 설계자는 별도의 문법을 고안하였습니다. 그것이 바로 인터페이스의 명시적 구현입니다.
인터페이스를 명시적으로 구현하는 문법은 간단합니다. 구현하는 메서드 이름 앞에 누구의 메서드인지만 표시해주면 됩니다. 다음 코드는 코드 4를 명시적 구현으로 바꾼 코드 입니다.
01 internal class Soldier : IGunner, ISoccerPlayer
02 {
03 void IGunner.Shoot()
04 {
05 Console.WriteLine("빵빵");
06 }
07
08 void ISoccerPlayer.Shoot()
09 {
10 Console.WriteLine("슛");
11 }
12 }
코드 5
3번 라인과 8번 라인을 보면 Shoot 메서드 앞에 각각 인터페이스의 이름을 붙이고 있습니다. 또한 접근 지정자가 사라졌습니다. 접근 지정자가 없기 때문에 Shoot은 private 멤버입니다. 이 부분에 대해서는 잠시 후 다시 말씀 드리겠습니다.
이제 군인이 총을 쏘고 공을 차는 코드를 보겠습니다.
1 private static void Main(string[] args)
2 {
3 Soldier soldier = new Soldier();
4 soldier.Shoot(); // 총 쏘기
5 soldier.Shoot(); // 공 차기
6 }
코드 6
코드를 작성하긴 했는데 영 기분이 이상합니다. 아니나 다를까 문법 에러라서 컴파일도 되지 않습니다. 코드 5의 Shoot 메서드는 Soldier의 private 메서드라서 접근할 수 없다고 합니다. 혹 접근이 된다고 하더라도, 4번 라인에서는 총을 쏘고 5번 라인에서는 공을 차야 하는데, 뭐가 무엇을 하라는 말인지 구별이 안 갑니다. 결론적으로 인터페이스가 명시적으로 구현이 되면 Soldier 변수를 통해서는 접근할 수가 없다는 사실을 알았습니다. 그렇다면 이 군인이 총을 쏘고 공을 차게 하는 방법은 무엇일까요?
답은 Soldier가 아닌 IGunner 혹은 ISoccerPlayer를 통해서 접근을 한다는 것입니다. 즉 총을 쏠 때는 Soldier 객체를 IGunner로 형변환 하고, 공을 찰 때는 ISoccerPlayer로 형변환 한다는 것입니다.
1 private static void Main(string[] args)
2 {
3 Soldier soldier = new Soldier();
4
5 ((IGunner)soldier).Shoot(); // 총 쏘기
6 ((ISoccerPlayer)soldier).Shoot(); // 공 차기
7 }
코드 7
형변환을 하지 않는다면 Soldier 객체를 만들어 IGunner 혹은 ISoccerPlayer 변수가 그를 참조하도록 하는 방법도 있습니다.
1 private static void Main(string[] args)
2 {
3 IGunner gunner = new Soldier();
4 gunner.Shoot(); // 총 쏘기
5
6 ISoccerPlayer soccerPlayer = new Soldier();
7 soccerPlayer.Shoot(); // 공 차기
8 }
코드 8
이 경우에는 예컨데 3번 라인과 같이 생성된 Soldier 객체를 IGunner 변수에 참조시키면 IGunner 변수를 통해서는 총만 쏘지 공을 찰 수는 없습니다.
이제 코드 5를 다시 보며 두 Shoot 메서드의 접근 지정자에 대해서 생각을 해 봅시다. 코드 6에서 확인하듯이 Soldier 클래스의 입장에서 두 Shoot 메서더는 private 입니다. 하지만 코드 7에서 보듯이 IGunner와 ISoccerPlayer의 입장에서는 각각의 Shoot 메서드는 public 입니다. 결국 명시적으로 구현된 인터페이스 메서드의 접근 지정자는 상황에 따라서 서로 다른 두 개의 접근 지정자를 가지게 되는 것입니다.
이제 인터페이스의 암시적, 명시적 구현을 모두 다루었으니, 응용으로 이 둘을 섞어 놓은 형태를 보도록 합시다. 다음 코드를 보시기 바랍니다.
01 internal class Soldier : IGunner, ISoccerPlayer
02 {
03 public void Shoot()
04 {
05 Console.WriteLine("빵빵");
06 }
07
08 void ISoccerPlayer.Shoot()
09 {
10 Console.WriteLine("슛");
11 }
12 }
코드 9
3번 라인은 암시적으로, 8번 라인은 명시적으로 인터페이스 멤버를 구현하고 있습니다. 8번 라인이야 ISoccerPlayer의 Shoot을 구현한다고 명확히 알 수 있겠는데, 3번 라인의 경우에는 명확히 알 수가 없습니다. 이 경우 컴파일러는 Soldier가 구현하여야 한는 Shoot는 두 개가 있는데 그 중 하나는 ISoccerPlayer의 Shoot이기 때문에(8번 라인) 나머지 하나는 IGunner의 Shoot로 인식을 합니다.(3번 라인)
이를 사용하는 코드는 다음과 같습니다.
01 private static void Main(string[] args)
02 {
03 Soldier soldier = new Soldier();
04
05 ((IGunner)soldier).Shoot(); // 공 차기
06 ((ISoccerPlayer)soldier).Shoot(); // 공 차기
07
08 soldier.Shoot(); // 총 쏘기
09 }
코드 10
코드 7과 달라진 점은 8번 라인과 같이 Soldier 변수를 통해서도 Shoot을 호출할 수 있다는 것입니다. 이 때 Soldier의 Shoot는 IGunner의 Shoot를 구현하고 있기 때문에 결국 8번 라인은 5번 라인과 동일한 결과가 나타납니다.
빵빵
슛
빵빵
계속하려면 아무 키나 누르십시오 . . .
인터페이스의 명시적 구현을 마무리 짓기 전에 한 가지 중요한 사실이 있습니다. 실컷 명시적 구현을 연습해 놓고 이런 말 하려니 좀 허탈하긴 한데, 바로 인터페이스의 명시적 구현은 ‘꼭 필요한 곳이 아니면 가급적 사용하지 말라’는 것입니다. 그 이유는 여태껏 이야기한 내용에 들어 있습니다. 예를 들어 Soldier 클래스를 사용하는 개발자는 Soldier 클래스만 봐서는 Shoot 메서드의 존재를 알기 어렵습니다. 또한 만일 Soldier가 클래스가 아닌 구조체라면, Shoot메서드를 호출하기 위해 ((IGunner)soldier).Shoot과 같이 형변환을 할 때 마다 추가적인 박싱이 일어나기도 합니다.
출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1470&page=7
.NET Framework 정규표현식 자습서
… 일반 문자열 자체를 의미한다.
예: abc - 'abc', 'aabc' (O)
. 개행 문자 (즉 “\n")를 제외한 임의의 문자 한 개와 매치한다.
예: a.c - 'abc' (O), 'ac' (X)
* 바로 앞에 있는 패턴이 0번 혹은 그 이상 반복되는 것을 의미한다.
예: ab*c - a + b가 0개 이상 + c
[] 대괄호 안에 있는 임의의 문자와 매치하는 문자 클래스이다. 그러나 첫 번째 문자가 캐럿(“^”)이면 대괄호 안에 있는 문자를 제외한 어떠한 문자와도 매치한다는 의미가 된다. 대시(“-”)가 대괄호 안에 존재하면 대시 앞뒤 문자 사이의 범위를 가리킨다. 예를 들어, “[0-9]”는 “[0123456789]”와 동일한 의미이다. 문자 클래스에 대시나 대괄호(“]”)를 포함할 수 있도록 하기 위해서, “[” 바로 다음에 나타난 문자가 “-” 혹은 “]”이면 각각의 특별한 의미 없이 문자 그대로 해석한다. POSIX는 비 영어권 문자를 다룰 때 유용한 특수 대괄호를 도입하였다. 대괄호 안에서는 “\” 문자로 시작하는 C 언어의 이스케이프 시퀀스를 제외한 나머지 모든 메타 문자들이 특별한 의미를 상실한다.
예: a[bcd]c - a + b, c, d 중 한 문자 + c
예: a[^bcd]c - a + b, c, d가 아닌 한 문자 + c
예: a[a-z]c - a + a ~ z 중 한 문자 + c
^ 행의 시작에서 정규 표현식의 첫 번째 문자가 매치한다. 한편 대괄호 안에서는 부정의 의미로 사용된다.
예: ^123 - 123, 1230 (O) | 0123 (X)
$ 행의 끝에서 정규 표현식의 마지막 문자가 매치한다.
예: 123$ - 123, 0123 (O) | 1230 (X)
{} 괄호 안에 하나 또는 두 개의 숫자를 담고 있을 경우 괄호 앞에 있는 패턴이 몇 번 매치해야 하는지를 가리킨다. 예를 들면 A{1, 3}의 경우 문자 A가 최소 한 번에서 연속 세 번까지 나타나야 함을 뜻한다. 중괄호 안에 특정 이름을 담고 있다면, 그 이름으로 대체한다는 것을 의미한다.
{n} {n} 바로 앞의 문자가 n개다.
예: ab{2}c - a + b가 두 개 + c
{n,} {n,} 바로 앞의 문자가 n개 이상이다.
예: ab{2,}c - a + b가 두 개 이상 + c
{n, m} {n, m} 바로 앞의 문자가 n개 이상 m개 이하이다.
예: ab{2,4}c - a + b가 두 개 이상 4개 이하 + c
\ 메타 문자들의 의미를 파기하고 C 언어 이스케이프 시퀀스를 표현하는 데 사용한다. 예를 들어, “\n"은 개행 문자이지만 ”\*“은 별표 그 자체를 나타낸다.
예: \*, \+, \^
+ 앞에 있는 정규 표현식이 한 번이나 그 이상 반복하여 매치할 수 있음을 의미한다. 예를 들면 [0-9]+ 와 같이 쓰일 수 있다. 이 패턴은 “1”, “111” 혹은 “123456”에 매치하지만, 빈 문자열에는 매치하지 않는다. 만약 플러스 기호 대신 별표를 이용하면 빈 문자열도 매치된다.
예: ab+c - a + b가 1개 이상 + c
? 앞에 있는 정규 표현식이 0번이나 한 번 나타날 수 있음을 의미한다. 예를 들어 -?[0-9]+ 와 같은 패턴은 숫자 앞에 마이너스 기호를 붙이거나 붙이지 않을지를 결정할 수 있다.
예: ab?c - a + b가 없거나 1개 + c
| 앞에 있는 정규 표현식 혹은 뒤에 있는 정규 표현식에 매치한다.
예: cow|pig|apple - cow 또는 pig 또는 apple
“...” 따옴표 안에 있는 문자들은 C 언어의 이스케이프 시퀀스를 제외한 모든 메타 문자의 의미를 파기하고 문자 그대로 해석한다.
/ ‘/’ 뒤에 있는 정규 표현식이 매치하는 경우에 한해 ‘/’ 앞의 정규 표현식에 매치한다. 예를 들면 0/1의 경우 “01”과 같은 문자열 안에 있는 “0”에 매치하지만, “0” 또는 “02”아 같은 문자열에는 매치하지 않는다. 슬래시 뒤에 있는 패턴과 매치하는 부분은 “사용되지” 않으며, 다음 토큰 검색 과정에서 사용된다. 패턴 한 개 당 슬래시 하나만 허용된다.
() 정규 표현식 여러 개를 새로운 정규 표현식 한 개로 묶는다. 예를 들어 (01)의 경우 문자 시퀀스 01을 의미한다. 괄호는 *, +, 그리고 |와 함께 사용하여 복잡한 패턴을 구성할 때 매우 유용하게 쓰인다.
(?<GroupName>...) 그룹 매칭을 할 때 <GroupName> 부분이 나중에 프로그래밍 방식으로 결과를 탐색할 때 기준으로 사용된다. 뒤이어오는 ... 부분에 식을 쓴다.
예: (?<First>[0-9]{6})\-(?<Last>[0-9]{7})
앞서 나열한 연산자 중에서 몇몇은 ([] 처럼) 문자 하나에 대해서만 작동하는 반면 다른 연산자는 정규 표현식 전체를 상대로 작동한다는 사실에 주목하기 바란다. 보통 복잡한 정규 표현식은 간단한 정규 표현식 여러 개를 모아서 만든다.
출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1547&page=4
라면 만들기를 적용한 예
웹써핑중에 라면 만들기 강좌를 활용한 좋은 예가 있어 소개해드릴까 합니다.
원래 라면 만들기 강좌 다음 버전은 어렵지 않으면서도 활용하기 좋은 다른 패턴을 쉽게 풀어보려고 했는데
한 놈만 제대로 이해한다면 다른 패턴들을 공부할때 쉽게 수긍 할 수 있는 부분이 많다고 봤습니다.
어째든.. 그냥 긁어다 붙이면 너무 성의가 없으니 기사를 적절히 끊어서 설명하겠습니다.
Continuing with this idea of showing alternatives using the same example, I thought it might be interesting to show the Decorator Pattern which can be used when the Policy Injection Application Block and PostSharp may be overkill for your application.
In the two examples mentioned above I created a Stopwatch Attribute that timed the ILogger.Write Method used in our console application:
public interface ILogger
{
void Write(string message);
}
public class ConsoleLogger : ILogger
{
public void Write(string message)
{
Console.WriteLine();
Console.WriteLine("*** In Logger ***");
Console.WriteLine(string.Format
("message : {0}", message));
}
}
참고로 이 기사를 쓴 분은 MS의 log 관련 application block을 쓰는 모양인데
저는 log4net을 씁니다. 그래서 관련 코드에대해서는 설명이 부족할 수 있습니다.
음.. 어째든 위 소스를 보니 ConsoleLogger는 ILogger라는 추상화된 컴퍼넌트를 구현하고 있군요.
아직 데코레이터 클래스는 나오지 않았습니다. 아마 이 놈이 라면이 될 모양입니다.
이 ConsoleLogger에 Write()라는 기능을 나중에 동적으로 추가시키려고 하는것이겠죠.
그런데 라면 만들기때는 abstract class를 사용했습니다. 왜냐하면 공통된 구현이 많아서
최상위 클래스에서 일부 구현을 해주는게 편했기 때문이죠. 어떤 객체를 추상화의 수준을
interface냐 abstract class냐는 상황에 따라 다릅니다. 거의 대부분이 Interface를 쓰지만요.
Creating these attributes is pretty simple for use in the Policy Injection Application Block and PostSharp. However, we can avoid the use of these 3rd party libraries by creating a StopwatchLoggerDecorator Class that will achieve the same results:
public class StopwatchLoggerDecorator : ILogger
{
private readonly ILogger _inner;
public StopwatchLoggerDecorator(ILogger inner)
{
_inner = inner;
}
public void Write(string message)
{
Stopwatch sw = new Stopwatch();
sw.Start();
_inner.Write(message);
sw.Stop();
Console.WriteLine();
Console.WriteLine(string.Format("Time Elapsed: {0} ms", sw.ElapsedMilliseconds));
}
}
아.. 바로 데코레이터가 만들어졌군요. 라면 만들기때와는 달리 데코레이터의 추상화는 없는것 같습니다.
이 경우를 단일 데코레이터라고 합니다. 그런데 라면 만들기때는 재료(데코레이터)가 다양했습니다.
다음과 같이 아무리 감싸도 결국 라면이되는 표현 new 재료2(new 재료1(new 라면()))..
이렇게 되기위해서 데코레이터는 자기가 감싸야 될 클래스의 추상화 클래스를 구현할 필요가 있었습니다.
c# 코드상에서 재료 as 라면과 같은 표현이 가능해야 된다고도 했었죠.
라면 만들기에서 재료라는 데코레이터는 다양했었고 각 재료들마다의 개별적인 메서드들이
있을수 있었기 때문에 재료라는 데코레이터의 추상화를 만들어주는것이 필요했습니다.
그리고 각 재료들은 그 추상화된 객체를 구현하도록 했었지요.
이것을 다중 데코레이터라고 합니다.
소스로 돌아가서.. 역시 이번 데코레이터도 ConsoleLogger가 구현한 ILogger를 구현했군요.
이건 공식에 가깝습니다. 이 패턴에서는 자기가 감싸게 될 놈의 추상화 객체를 구현해야만 합니다.
이 데코레이터는 Write()가 수행되는 시간을 계산하는 기능이 추가되어있습니다.
핵심은 역시 생성자에서 ILogger라는 자기가 감싸게 될 추상화 객체를 인자로 받는 것이죠.
이런식으로 내부적으로 다른 객체를 생성하지 않고 관계를 맺는것이 모든 패턴들의 특징입니다.
(이런 관계를 is-a 관계라고 하고 반대로 객체안에서 객체를 생성하게 되는것을 has-a관계라고 합니다.)
우리는 Write() 기능이 데코레이터 되었다는걸 명확히 알 수 있습니다.
ILogger를 구현한 클래스의 Write()를 재사용하면서 그 기능에 스톱와치 클래스의 기능까지 추가했습니다.
그런데 Stopwatch라는 클래스는 우리가 소스를 건들일수 없는 상용 컴퍼넌트였다면..
생각해보세요. Log에 그 기능을 추가하기 위해선 applicatoin block의 소스를 뜯어 고치던가 했어야 했던지
합치지 못하고 분리해서 호출했어야 할지도 모르죠. 데코레이터 패턴을 안다면 그런 생각자체를 못하겠지만..
어째든 이름도 잘 지었네요. 이 데코레이터의 이름은 그래서 StopwatchLoggerDecorator 이군요.
이런식으로 네이밍 하는것은 권장되는 사항입니다. 코드만 봐도 그 의도가 드러나기 때문에
따로 주석을 쓸 이유도 없습니다.
아래에 이 데코레이터를 적용한 예가 있습니다.
The Decorator Class implements the same ILogger Interface and takes the "real" ILogger in its constructor, ConsoleLogger. Inevitably it passes the Write Call onto the real logger, but before doing so starts a Stopwatch. After the call to ConsoleLogger is finished ( _inner.Write ), it stops the Stopwatch and displays the elapsed time in the console.
In keeping with the examples mentioned above, one can register the ILogger in your IoC Container / Dependency Injection Tool of choice. Here is an example using Unity:
IUnityContainer container = new UnityContainer();
// Register Logger
ILogger consoleLogger = new ConsoleLogger();
ILogger logger = new StopwatchLoggerDecorator(consoleLogger);
container.RegisterInstance<ILogger>(logger,
new ContainerControlledLifetimeManager());
// Use Logger
var registeredLogger = container.Resolve<ILogger>();
registeredLogger.Write("Hello!");
Console.ReadLine();
The results are as follows:
위 소스에서 데코레이터를 적용한 부분은 아래와 같습니다.
ILogger consoleLogger = new ConsoleLogger();
ILogger logger = new StopwatchLoggerDecorator(consoleLogger);
이 부분만 보세요. 라면 만들기때와 마찬가지로 라면을 재료로 요리했군요.
간단히 logger.Write(); 써주면 결과화면처럼 나오게 됩니다.
(Write()의 호출이 원문과 다르긴 하지만.. 실제로 저렇게 써도 되는거니 넘어가죠. -_-)
이 기사를 쓴 목적이 ConsoleLogger를 변경하지 않고도 새로운 기능을 추가하는것이였는데 성공적입니다.
그런데 우리의 고객이 이왕 출력되는거 html 출력으로도 보고 싶어합니다.
여러분은 어쩌시겠습니까? Write() 메서드를 오버로딩하시겠다고요? 왜이러시나요..
(한번 해보세요.. 되는지.. -_-)
Write() 메서드에 인자를 받게 만들어 내부에서 if를 써서 분기를 하시겠다고요? 역시 안됩니다.. -_-
HTMLLoggerDecorator를 만드셔야죠. 그리곤 사용은 아래와 같이 쓸수있어야 합니다.
new HTMLLoggerDecorator(consoleLogger);
또는 new HTMLLoggerDecorator(new StopwatchLoggerDecorator(consoleLogger))
이 HTMLLoggerDecorator는 어떻게 구현해야 할까요? (html 태그를 어렵게 적용시키려 하지 마세요-_-)
참고로.. new StopwatchLoggerDecorator(new HTMLLoggerDecorator(consoleLogger))와 같이 쓰게되면
이상한 출력결과가 나올수 있습니다. 감싸는 순서가 HTMLLoggerDecorator를 만든 의도와는 달리 사용이
되었기 때문인데요. 이런 문제들은 팩토리나 빌더같은 다른 생성패턴들과 결합해서 써야 될 필요성이
있습니다. 팩토리나 빌더는 다음에 좋은 예가 떠오르면 글을 올려보도록 하겠습니다.
이 글이 패턴을 이해하는데 조금이나마 도움이 되었기를 바랍니다.
전체 기사 링크
http://codebetter.com/blogs/david.hayden/archive/2008/09/30/decorator-pattern.aspx
출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1635&page=1
라면만들기
혹시 일본식 라면집에 가보셨나요? 저는 여자친구와 갔다가 곰탕같은 라면을 시켜먹었는데(으웩...) 그게 8년전 애기입니다.
개인적으로 라면은 우리나라, 삼양라면을 좋아합니다. 가끔 너구리도 먹어주시면 흐뭇해지고요. 그리고 일요일엔 짜파게티죠~
오늘 이 자리를 빌어 저와같이 라면을 좋아하시는(돈이 없어 라면을 드시던간에-_-) 우리나라 프로그래머님들을 위해
저만의 비법을 알려드리겠습니다. 물론 어떤 댓가를 바라진 않습니다. 다만 응용한 결과를 저에게도 알려주시면 고마울뿐이죠.
아래는 제가 만든 첫번째 라면입니다.
소스를 보니 텅~ 빈 생성자 하나에 설명이라는 속성 그리고 가격이라는 메소드가 있군요.
설명이 무안 할 정도로 단순하지만.. 얼마나 복잡하게 될 수 있는지 보기로 하죠.
이제 이런 형태로 된장라면, 간장라면, 곰탕라면, 짜장라면, 김치라면, 카레라면, 조개라면, 사노라면, 짬뽕라면,
된장조개라면, 고추조개라면, 짜장된장라면, 고추짜장라면, 고추된장카레조개짬뽕라면.. 등등등..
지면상-_- 나머지 라면들을 나열하는것은 생략하고 이 정도만 만들어 보죠.
대충 복사해서 이름하고 가격만 바꿔주면 될듯 보입니다.
자.. 이제 평소에 잘하시는 ctrl+c, ctrl+v 신공을 발휘할때가 왔네요.
그런데 가만 들여다보면 단순하진 않습니다. 그렇게 쉽게 해결 될 문제가 아닌거죠.
그 이유로 고추조개라면의 경우로 예를 들면 가격()을 구현해야 하는데
아래 소스에서 보다시피 참조되는 라면들의 가격을 반영해야 합니다.
만약 고추/된장/카레/조개/간장/양파/해삼/멍게/오징어/짬뽕라면 이라면... 그외
카레/조개/간장/양파/해삼/멍게라면, 해삼/멍게/오징어/짬뽕라면 등등 다양한 조합들이 있으므로
가격이란 메소드 작성이 쉽지 않습니다.
더구나 이 글을 보신 분들의 요청에 의해 라면의 종류는 더 늘고 복잡해질지 모릅니다.
... 왜 문제가 되는지 생략 ...
여기에 이 문제를 해결 할 비법이 있습니다.
우선 라면에대해 다시 생각해 볼 필요가 있습니다.
다 알다시피 라면은 어떤 재료가 들어가냐에 따라서 종류가 달라지고 어떤 라면이 될지가 결정됩니다.
그리고 재료는 하나이상 복합적으로 들어갈 수 있고(안들어갈수도 있군요) 이에따라 라면의 가격도 달라집니다.
이 둘의 관계는 달리말하면 라면은 재료에 함수적 종속성을 가지게 됩니다. 재료가 라면을 결정짓게 되는것이죠.
따라서 우리는 라면과 재료의 관계를 재정립하고 이를 코드로 분리해서 관리 할 필요성이 있습니다.
라면은 재료에의해 설명도 가격도 달라지므로 둘을 합치거나 또는 서로가 포함관계에 있으면 코드의 중복과
복잡도가 증가하고 재료비나 또는 가격을 계산하는 로직이 변경될 경우 수정에 많은 힘이 드는 상황이 발생합니다.
해결책은 아래와 같이 라면과 재료라는 추상화된 객체를 만드는것으로부터 시작합니다.
여기서 핵심은 재료로 라면에 어떠한 행위를 한 뒤에라도 라면으로써 사용이 가능해야 합니다. (현실도 그렇죠)
기본라면에 어떤 재료를 넣어 만들어진 라면도 라면이고, 그렇게 만들어진 라면에 다시 재료를 추가해도 라면이라는 말이죠.
따라서 재료 클래스는 추상화된 라면 클래스를 구현또는 상속받아야 합니다. 재료 as 라면이 성립되어야 하니까요.
또 재료만의 속성과 메소드를 가질수 있으니 이것을 객체로 만드는데는 이의가 없습니다.
(인터페이스로 구현해도 됩니다. 하지만 여기선 추상클래스로..)
이걸 바탕으로 아래와 같이 고추라는 재료를 만들수 있습니다.
재료의 생성자에 가공(요리) 할 대상인 라면의 레퍼런스를 넘겨주는것에 주목 할 필요가 있습니다.
설명 속성은 넘겨받은 라면에 재료명을 더해주는것만으로 깔끔하게 완성된 코드입니다.
마찬가지로 가격 메소드 또한 가공(요리) 할 라면가격에 재료(고추)의 가격을 더해주면 됩니다.
이렇게 기본라면에 고추를 넣은 후에도 라면으로봐야죠. 가공(요리)된 라면에 다시 고추를 넣어도 라면입니다.
그럼 고추고추라면이 되겠군요. -_-
끝에가면 예제가 있으나 우선 사용예를 보겠습니다.
라면 고추라면 = new 고추( new 라면인스턴스() );
라면 고추고추라면 = new 고추( new 고추( new 라면인스턴스() ) );
고추고추라면.가격() 메서드를 호출하면 ( 고추재료가격 + ( 고추재료가격 + 라면가격) ) 과 같은 계산이 이뤄지게 됩니다.
그럼 고추와 같은 방법으로 조개라는 재료도 만들어보죠.
자.. 이제 재료도 대충 만들었으니 재료들을 넣을 라면을 만들어 볼 차례입니다.
아직 실체화된 라면이 없었습니다. 여기선 그냥 삼양라면을 만들어 보겠습니다.
이제서야 라면을 요리 할 시간이 되었군요.
재료도 구비되었으니 이제 라면을 재료로 새롭게 재탄생시킬수 있습니다.
라면을 고추로 감싸서 고추라면을,
라면을 조개로 감싸고 다시 고추로 감싸니까
고추조개삼양라면이 만들어집니다.
고추조개삼양라면을 양파라는 재료 클래스로 감싸주면?
양파고추조개삼양라면이 되겠죠.
실행 결과화면
어떤가요?
라면 클래스(여기서는 추상화된 라면 클래스를 구현한 삼양라면을 말합니다) 자체는 손대지 않았습니다.
단지 재료로 감싸준것 뿐이죠. 또 단가의 계산도 간결하게 해결 되었습니다.
그런데 결과화면을 보니 고추/조개/삼양라면 순이 아니라 읽고 부르기엔 웬지 이상합니다.
이 문제를 수정하기 위해 기존 클래스들을 수정해야 할까요?
우리는 방금 원본 클래스에 손도 대지않고 원하는 기능을 구현하는 방법을 배웠습니다.
따라서 이번에도 그런 방식으로 해보는게 좋겠군요.
아래와 같이 DescriptionDeco라는 클래스를 만들어봤습니다.
역시 여기서의 핵심도 라면이라는 추상화 클래스를 상속받고 생성자에선 가공해야 할 라면 클래스를 넘겨받는것이죠.
이렇게 하면 기존 코드의 변경이 일어나지 않습니다.
(단지 설명을 출력하는 기능에대한 확장이므로 가격은 변동이 없습니다. 공짜란 말이죠.)
적용 예입니다.
결과화면
* 실행가능한 전체소스를 첨부하였으니 참고하세요.
마무리
별로 쓴 내용도 없는데 이걸 작성하는데 2시간이나 걸렸네요. 원래는 대충 설명하고 비주얼한 예제를 쓸려고 했는데
이 속도로는 어렵겠군요. -_- 이외에도 좀 더 많은 할 애기가 있지만 여기서 끊는게 좋겠습니다.
아시는 분들은 이미 아셨을수도 있는데 위 내용은 Head First Design Patterns 책을 약간 패러디한것으로
데코레이션 패턴에 해당하는 내용입니다. 미숙한 글이나마 도움이 되었으면 합니다.
용어정리
추상화 클래스인 라면을 Component,
이 클래스를 상속받아 구현된 삼양라면을 ConcreateComponent라고 합니다.
Decorator는 추상화된 재료 클래스 입니다.
일반적으로 Component는 어떤 행위를 정의한 인터페이스와 같이 추상화된 객체일 수 있습니다.
이걸 상속받아 구현된 ConcreateComponent는 Decorator가 감싸게 될 클래스이고요.
Decorator Pattern의 경우 객체의 변형없이 상속또는 구현을 통해 객체의 기능을
유연하게 확장 할 수 있는 방법으로 자주 사용됩니다.
실무에서 부담없이 적용해 볼 만한 몇개 안되는 패턴중에 하나입니다.
출처 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=18&MAEULNO=8&no=1627&page=1
Boxing과 Unboxing
개 요
이번에는 닷넷의 특징이라고 할 수 있는 Boxing, Unboxing에 대해 배워보도록 하겠습니다.
내부적으로 일어나는 일이기때문에 처음에는 몰라도 상관없지만 퍼포먼스에 좀 더 신경쓰기 시작하면 반드시 이해해야할 개념이라 할 수 있습니다.
MSIL 코드를 직접 보면서 Boxing, Unboxing 을 이해해 봅니다.
Boxing
Value 타입의 변수는 Stack에 생성되고 Reference 타입의 변수는 Heap에 생성됩니다.
Heap에 무엇인가가 생성될 때 변수는 이 heap에 생성된 객체를 가르키는 4byte "포인터(주소값)"를 할당받게 됩니다.
그래서 이 변수를 다른 함수의 매개 변수로 넘길 때는 항상 객체 자체가 아니라 이 포인터만 넘기게 됩니다.
value 타입의 변수 경우는 어떤 함수의 매개 변수가 될 경우 이 값의 copy를 만들고 copy를 통째로 넘겨주게 됩니다.
여기까지는 c++ 기반 개발자라면 이미 누구나 알고 있는 사항일 것입니다.
그러나 닷넷에선 Value 타입의 변수를 Reference 타입의 변수인것처럼 사용하는 경우가 있습니다.
그리고 바로 이 때 "Boxing"이 이루어집니다.
자 다음과 같은 예를 들어보도록 하겠습니다.
public void Method()
{
ArrayList ar = new ArrayList();
ar.Add(5);
}
ArrayList.Add() 함수의 매개변수로 요구하는 것은 분명 Reference 형인 Object입니다.
그런데 value 타입인 integer 값을 함수의 매개 변수로 사용하고 있군요.
바로 위와 같은 상황처럼 Value 타입의 변수를 Reference 타입인양 사용할 경우에 Boxing이 일어납니다.
IL코드를 확인해 볼까요?
.method public hidebysig instance void Method() cil managed
{
// Code Size: 20 byte(s)
.maxstack 2
.locals (
[mscorlib]System.Collections.ArrayList list1)
L_0000: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldc.i4.5
L_0008: box int32
L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
L_0012: pop
L_0013: ret
}
AL_0007에서 4바이트 정수 5를 스택으로 로드하고 있습니다.
그리고 다음줄에서 우리가 기다리던 boxing을 하는군요.
ArrayList에 실제로 저장되는 값은 뭘까요?
앞에서 말씀드린 바와 같이 Add()함수의 매개 변수는 Object이기 때문에 5라는 값이 아니라 4바이트의 주소 포인터를 저장해야 한다는 것을 우리는 이미 알고 있습니다.
integer가 object로 변환되는 과정이 필요하겠군요.
integer가 가지고 있던 5라는 '값'은 힙에 생성한 새로운 주소 공간으로 copy되고 그 값이 저장된 주소를 리턴받습니다.
value 타입의 integer(int32)가 reference 타입의 object가 변환되었습니다.
이렇게 닷넷에서 우리가 모르는 사이에 value 타입의 변수를 reference처럼 사용할 수 있도록 해주는 것을 boxing이라고 합니다.
Unboxing
자 그럼 이제 Unboxing 에 대해 알아보도록 하겠습니다.
Unboxing은 간단히 boxing의 반대 과정이라고 보시면 됩니다.
public void Method()
{
ArrayList ar = new ArrayList();
ar.Add(5);
int b;
b = (int)ar[0];
}
Unboxing이란 object에 저장된 주소값으로부터 '값'을 얻어다 스택에 생성해둔 변수 b에 copy하는 과정입니다.
마찬가지로 IL 코드에 unbox라고 나오는걸 보실수 있습니다.
.method public hidebysig instance void Method() cil managed
{
// Code Size: 34 byte(s)
.maxstack 3
.locals (
[mscorlib]System.Collections.ArrayList list1,
L_0000: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldc.i4.5
L_0008: box int32
L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
L_0012: pop
L_0013: ldloc.0
L_0014: ldc.i4.0
L_0015: callvirt instance object [mscorlib]System.Collections.ArrayList::get_Item(int32)
L_001a: unbox int32
L_001f: ldind.i4
L_0020: stloc.1
L_0021: ret
}
쉽게 이해가 가시리라고 봅니다. :)
정리
boxing과 unboxing을 가능하면 피하는게 좋다는 말을 한번쯤 들어보셨을 겁니다.
위에서 보신것처럼 boxing, unboxing이란 닷넷의 배려는 우리가 편하게 프로그램을 짤 수 있도록 했는지는 모르지만
IL 코드에서 확인할 수 있는 것처럼 더 많은 과정을 거치고, 메모리를 사용하고, 속도가 느려지게끔하는 효과를 가져올 수 있습니다.
boxing하는 과정에서 값을 copy하기 위해 힙에 주소 공간을 확보해야 한다는 것을 기억하실 겁니다.
잦은 boxing은 사용되지 않는 리소스를 정리하기 위해 비용이 큰 Garbage Collector가 동작하는 주기를 더 짧아지게 할 수 있습니다.
그렇다면 불필요한 boxing을 막으려면? boxing이란 결국 value type의 변수를 reference type인 object로 암시적으로 변환하는 것입니다.
만약 객체가 boxing될 것이 자명하다면 처음부터 object를 사용하면 되겠죠.
만약 사용하는 value type의 변수가 32비트 이상이고(크기가 크면 클수록 복사될 때 더 큰 비용을 사용합니다)
boxing, unboxing이 자주 일어나는 구조라면 struct(value type)가 아니라 class(reference type) 를 사용하는 것이 유리합니다.
때문에 객체의 타입을 정할 때는 객체의 크기와 얼마나 자주 copy되는지를 고려해야 할 것입니다.
Boxing과 Unboxing의 모든 것
오늘 강좌는 Boxing과 Unboxing이라는 개념에 대해서 살펴볼 까 합니다. 이것은 일전의 "형 변환을 기반으로 하는 리플렉션"과 관련성이 있는 내용입니다. 그리하여 실질적인 내용을 따져보면 System.ValueType에 관한 특수한 리플렉션 기술이 Boxing과 Unboxing에 해당됩니다.
System.ValueType 형식은 메타 데이터 기반이 아닌 "낮은 수준"의 데이터 형식들의 추상 형식입니다. 여기로부터 파생되었다고 알려지는 주요 데이터 형식으로는, System.Byte, System.Int16, System.Int32, System.Int64, System.Float, System.Double 등이 있습니다. 하지만 이러한 데이터 형식들은 메타 데이터 기반이 아닙니다. 정확한 풀이로는 힙에 할당되며 크기 또한 자유자재로 변형될 수 있는 형식들입니다. 메타 데이터로 표기하는 것은 엄청난 오버로드를 수반하게 되므로 부적절합니다. 하지만 .NET Framework는 이를 매끄럽게 처리하게 되어있는데 그것이 Boxing과 Unboxing이라 불리우는 기술입니다.
Boxing과 Unboxing의 의미 풀이부터 해보도록 합니다. 두 단어 모두 Box 라는 키워드를 포함하는데, 쉽게 생각할 수 있습니다. 힙에 할당된 연속적인 데이터를 편리하게 관리하기 위하여 포장을 해놓는다 라는 의미에서 Box를 떠올리면 쉽습니다. 즉, 힙에 할당된 연속적인 데이터를 메타 데이터가 이해할 수 있는 형태로 포장해준다는 의미입니다. 그러면 실제 코딩을 살펴보도록 하지요.
int i = 123;
object o = (object)i;
위의 두 코드를 살펴보면, 사실 아무런 의미는 없습니다. 하지만 이것이 Boxing의 대표적인 예입니다. 이러한 코딩이 가능함으로서 얻을 수 있는 이점이 대단히 큽니다. Boxing을 가장 잘 활용하는 예는 ADO .NET 관련 클래스들입니다. 데이터 베이스 시스템이 질의에 대한 결과로 반환하는 데이터 형식을 한꺼번에 다루기 위한 방법으로 Boxing과 Unboxing을 복합적으로 활용합니다.
i 라는 변수에 123이라는 정수 값을 대입했습니다. 그 다음, o 라는 하나의 추상 객체를 선언하여 형식 변환을 취하였습니다. 코드 상에서는 분명히 형식 변환으로 표기되었습니다만 사실 이 둘은 형식 변환이 일어날 수 없는 관계입니다. object는 메타 데이터로 표기되는 데이터만을 다룰 수 있지만 int는 힙에 할당되는 연속적인 데이터 스트림입니다. 서로 관계가 없지만 이것이 가능했던 이유가 바로 Boxing입니다.
int x = (int)o; // Okay
long y = (int)o; // Okay
short z = (int)o; // Error!
x는 i와 같은 int 형식이고, y는 int 형식보다 큰 범위의 수를 다룰 수 있는 long 형식이며, z는 int 형식보다는 작은 범위의 수를 다루는 short 형식입니다. 세 동작 모두 object 형식을 int 형식으로 바꾸는 동시에 Unboxing을 수행하게 되었습니다. 즉, object 형식으로 포장된 데이터의 원래 내용물을 대입한 것입니다. 하지만 x, y와는 다르게 z는 컴파일 오류를 낼 것입니다.
x는 손실 변환이 일어나지 않았으며 원래의 형식 그대로를 수용하였습니다. y는 원래의 i가 요구하던 정수 범위보다 더 큰 정수 범위를 지원하게 되어 확장 변환이 일어났습니다. 이 경우 두 가지 의미로 해석이 가능한데, 말 그대로 가능성을 위하여 예약된 확장 변환일 수 있지만 반대로 불필요한 공간이 더 많이 할당된 오버헤드 변환이기도 합니다. 하지만 z는 컴파일 오류를 냅니다. 손실 변환으로 다루어질 수도 있겠지만 Unboxing의 정의에 의하면 원래 가지고 있던 데이터 형식보다 범위가 더 적어졌으므로 메모리 구조와는 일치하지 않는 것으로 해석됩니다.
int a = o as int; // Error
형 변환에 사용하는 as 키워드로 변환을 시도해 보았습니다. 하지만 오류가 나게됩니다. 왜 일까요? as 형식으로 형변환이 가능하다는 것은 메타 데이터에 한정된 내용입니다. 따라서, System.ValueType으로부터 상속받은 모든 형태의 값 형식에서는 as로 형변환을 하거나 as로 Unboxing되지 않게 되었습니다.
as를 사용하고자 하였던 의도가 예외를 Throw 하지 않고 null을 대입하려 했던 것이었다면 null을 대입하지 않는 대신 다음과 같은 방법으로 처리하는게 좋습니다.
try { int a = (int)o; }
catch { /* 예외 처리 코드 */ }
[출처] Boxing과 Unboxing의 모든 것|작성자 독선생