4. Assembly

CLR 2010. 1. 15. 09:00 Posted by 알 수 없는 사용자
Assembly 라고 하면 맨 처음 생각 나는게 뭔가요? 저는 Assembly 언어가 생각이 납니다. 기계어와 Assembly 언어. 요사이는 Assembly 언어로 프로그램을 작성하는 분이 거의 없을 거라고 보지만 예전에 PC 사양이 많이 안 좋았던 시절에는 컴파일러의 최적화 성능도 그리 좋지 않았기 때문에 최적화된 코드로 빠른 속도를 내기 위해서 많은 부분을 직접 Assembly 언어로 구현했다는 이야기를 많이 들었었지요. 참 대단합니다. ㅎㅎ

아.. 이야기가 딴 길로 샙니다. 지난 주에 Metadata를 까보면서 넘어갔던 부분이 몇 개 있었는데요. 그 중의 하나가 Assembly 였지요. 오늘은 이 Assembly에 대해 공부해 보려고 합니다. 

Assembly?

Assembly 는 뭔가요? 지난 주에 저희가 만들었던 hi.exe 도 어셈블리입니다. 그럼 어셈블리는 실행 파일인가요? 틀린 말은 아니지만 좀 더 정확히 정의하자면 Assembly는 타입이나 리소스를 포함한 하나 이상의 파일들이 모인 파일의 컬렉션 이라고 CLR Via c# 2nd 한글판에서는 이야기하고 있습니다. 물론 일반적으론 하나의 파일로 구성되는 경우가 대부분입니다. Assembly를 논리적인 DLL 또는 EXE 파일로 이해하면 될 것 같습니다.

이 Assembly 파일이 갖고 있는 정보 중의 하나는 Manifest라고 하는데 이 Manifest에는 Assembly의 버전과 컬처, 배포자, public 형태의 타입, Assembly를 구성하는 모든 파일의 정보를 갖고 있습니다. CLR은 이 Assembly를 로드하여 지난주에 살펴본 Metadata 테이블을 읽은 후, Manifest에 있는 다른 파일들의 정보를 읽게 됩니다.

좀 어렵네요. 왜 이런 Assembly를 사용하는가. 를 먼저 살펴봅시다. Assembly는 다음과 같은 특징을 갖습니다.

* 개발된 코드를 여러 파일로 분산하여 실행 시 최소한의 파일로 구성한 다음, 필요한 파일이 생길 경우 인터넷 상으로 다운로드하는 식의 효과적인 배포가 가능합니다. (어플리케이션 설정 파일의 codebase 항목에서 설정된 곳을 통해 필요한 Assembly를 다운로드 할 수 있도록 설정이 가능합니다.)

* Assembly에 리소스나 데이터 파일을 추가할 수 있습니다. 이 데이터 파일의 형식은 어플리케이션이 파싱하여 이해할 수 있는 파일이라면 제약이 없습니다.(예 : 오피스 워드 파일, 엑셀 파일 등등)

* 서로 다른 언어로 구현된 타입(클래스)의 구성으로 Assembly를 생성하는 것이 가능합니다. 전체 모듈 중 일부는 C#으로 다른 일부는 Visual Basic으로 구현하는 것이 가능합니다. 어떤 언어로 작성되었던 컴파일 하게 되면 같은 IL코드로 생성이 되어 있으며 Assembly 테이블을 참조하면 해당 모듈을 재사용하는데 필요한 모든 정보를 얻을 수 있기 때문입니다.


Assembly는 Manifest Metadata 테이블이라는 것을 가지고 있습니다. 테이블 리스트는 다음과 같습니다.

 테이블 이름 설명 
 Assembly Assembly를 식별하는 단일 엔트리를 가집니다. 이 엔트리에는 어셈블리의 이름, 버전, 컬처, 플래그, 해시 알고리즘, 발행자의 공용키를 포함합니다. 
 File Assembly의 부분인 각 PE 파일과 리소스 파일을 위한 항목을 갖고 있습니다. 엔트리에는 파일의 이름, 확장자, 해시, 플래그를 포함합니다. 만일 Assembly가 오직 자신 하나의 파일만으로 구성된다면 이 테이블이 갖고 있는 엔트리는 없습니다. 
 ManifestResource 각 리소스를 위한 항목을 가집니다. 엔트리는 리소스 이름, 플래그(public, private)를 포함합니다.  
 ExportedTypes Assembly의 PE 모듈로부터 노출된 각 공용 타입을 위한 항목을 가집니다. 엔트리는 타입 이름, File 테이블의 인덱스,  그리고 TypeDef 테이블의 인덱스를 포함합니다.

이러한 manifest를 정의함으로써 Assembly의 파일 구성에 대한 정보를 제공하는 것이 가능하고 어셈블리 스스로 설명하는 것이 가능해집니다.

컴파일러 옵션에서 /t 의 값으로 /t[arget]:exe, /t[arget]:winexe, /t[arget]:library 를 주게 되면 컴파일러는 manifest정보를 포함한 단일 파일의 Assembly를 생성하게 됩니다. 이는 각각 CUI 실행 파일, GUI 실행 파일, DLL 라이브러리 입니다.

이외에도 /t[arget]:module 이라는 옵션도 있습니다. 이 옵션을 사용하게 되면 컴파일러는 manifest와 metadata를 포함하지 않은 PE파일을 생성하게 됩니다. 이 파일은 기본적으로 .netmodule 의 확장자를 가지게 되며 다중 파일로 구성된 Assembly를 구성할 때 사용하게 됩니다. 이 타입의 모듈을 CLR이 이용하기 위해서는 항상 이 해당 모듈이 Assembly에 포함되어 있어야 합니다. 이렇게 .netmodule 형식의 파일을 생성해 놓음으로써 자주 사용하는 메소드와 그렇지 않은 메소드를 구분하여 분리해서 파일 구성을 할 수도 있겠군요.

다음과 같은 코드를 컴파일 하려고 합니다.

코드 1) Hellovb.vb
NameSpace Hello
Public Class HelloWorld
Overloads Shared Sub PrintHello()
System.Console.WriteLine("HELLO!!")
End Sub
End Class
End NameSpace 

코드 2) Test.cs
using Hello;
public sealed class Program
{
public static void Main()
{
System.Console.WriteLine("Hi");
HelloWorld.PrintHello();
}

코드 1은 VB.NET용 소스 파일이며 코드 2는 C#용 소스 파일입니다.

코드 1은 .netModule로 컴파일을 해 봅시다.


코드 2 는 코드 1에서 정의된 HelloWorld 클래스의 PrintHello 메소드를 이용하고 있습니다. 방금 생성한 hellovb.netModule을 끌어와야 정상적으로 컴파일이 되겠지요. 생성하려는 Assembly에 모듈을 추가하려고 할 때는 /addmodule 이라는 옵션을 사용하면 됩니다.


test.exe라는 Assembly를 만들고 실행을 시키자 코드대로 잘 실행이 됩니다. Assembly의 특징 중 하나인 서로 다른 언어로 구현된 타입(클래스)의 구성으로 Assembly를 생성하는 것이 정말 가능하네요. :)

자 이제 우리는 생성된 Hellovb.netmodule 과 test.exe 파일을 열어봐서 앞에서 살펴본 내용을 확인해 보면 되겠습니다.

먼저 Hellovb.netmodule의 Metadata 엔트리를 봅니다.


앞에서 /t[arget]:module 이라는 옵션을 이용하여 PE파일을 생성하게 되면 컴파일러는 Manifest 정보를 포함하지 않는 PE 파일을 생성한다고 했습니다. 정말 manifest 정보가 없네요.

아 여기에 보이는 AssemblyRef 테이블은 현재 파일이 참조한 모든 참조 어셈블리 파일의 목록을 갖고 있습니다. 이 목록을 이용하여 Assembly의 다른 파일들을 열지 않고도 참조된 어셈블리가 어떤 것들인지 확인 할 수가 있는 것입니다. 이 파일은 기본 모듈인 mscorlib과 Visual Basic으로 만들어진 모듈이기에 Microsoft.VisualBasic이라는 Assembly를 참조하고 있다고 표현하고 있네요.

자 이제는 test.exe을 살펴봅시다.


테이블이 많이 보이네요. 앞에서 언급한 Assembly, FIle, ExportedTypes 테이블이 보입니다. ManifestResource는 없네요. 이 파일엔 리소스가 없거든요 :(
하나하나 살펴봅니다. 먼저 Assembly 테이블을 보니 이름(Name), 버전(Major, Minor, Build, Revision), Culture, Flag, 해시 알고리즘(hashAlgId), 발행자의 공용키(PublicKey) 값이 보입니다.


이번에는 File 테이블입니다. Test.exe Assembly의 부분인 다른 PE 파일의 정보가 들어있네요. 이름(Name : Hellovb.netmodule), 플래그(Flags : 이 파일은 리소스 파일이 아님을 알 수 있습니다.) 등을 확인할 수 있습니다.

마지막으로 ExportedType 테이블입니다. 타입의 이름(TypeName : HelloWorld), File테이블의 인덱스(Implementation)등을 확인 할 수 있습니다. TypeDefID 값이 0200002라고 적혀 있네요. Ildsam.exe 를 이용해서 hellovb.netmodule을 찾아보니(TypeName이 HelloWorld고 Namespace가 Hello니까요) 다음과 같이 ID를 확인할 수가 있군요.

아 복잡하네요. 간단히 말하자면 컴파일러는 컴파일을 하면서 Assembly를 생성하게 되며 이 Assembly들의 내부 Manifest정보와 Metadata정보 덕분에 Assembly 스스로 자신에 대한 설명이 가능하므로 어디서든지 쉽게 재 사용이 가능하구나! 라고 이해하면 될 것 같습니다.

Assembly 구성에 대해 좀 더 알아봅시다. 다음의 코드를 컴파일하여 Assembly 구성을 하되 DLL 라이브러리로 구성합니다.

코드 3) Test2.cs

using Hello;

public class GoodbyeWorld
{
public static void PrintGoodbye()
{
System.Console.WriteLine("Goodbye World");
}
}

public sealed class Program
{
public static void Main()
{
System.Console.WriteLine("Hi");
HelloWorld.PrintHello();
GoodbyeWorld.PrintGoodbye();
}

}

컴파일을 하니 test2.dll 이라는 라이브러리 모듈이 생성되었네요. 

이 라이브러리는 그렇다면 다른 곳에서 어떻게 사용하면 될까요? 물론 지금 해왔던 것 처럼 컴파일 시mscorlib.dll을 /r[eference] 옵션을 이용하여 추가해 컴파일 했던 것 처럼 /r[eference] 옵션을 이용하여 이 모듈을 사용할 수 있을 것입니다.

앞으로 우리가 개발할 때 사용할 툴인 Visual Studio IDE에서는 어떻게 사용하나요? 물론 다 알고 계시겠지만 프로젝트에서 참조 추가(Add Referece) 메뉴를 이용하면 쉽게 이 라이브러리를 사용할 수 있습니다.
우리가 구현했던 메소드들이 잘 보이네요 :)

컴파일러말고도 Assembly를 생성할 수 있는 방법이 또 있습니다. Assembly Linker(AL.exe)를 이용해서 생성할 수 있다고 합니다. Assembly Linker는 서로 다른 컴파일러에 의해서 빌드된 모듈들로 구성된 어셈블리를 만들 때 사용됩니다.

test3.cs 에서 Hellovb에서 사용되었던 코드를 제외하여 test4.cs를 만들고 이걸 netmodule로 컴파일을 하겠습니다.
public class GoodbyeWorld
{
public static void PrintGoodbye()
{
System.Console.WriteLine("Goodbye World");
}
}

public sealed class Program
{
public static void Main()
{
System.Console.WriteLine("Hi");
GoodbyeWorld.PrintGoodbye();
}

}

그리고 앞에서 생성했던 VB.NET의 Hellovb.netmodule과 지금 생성한 C#으로 만든 test3.netmodule을 AL.exe를 이용하여 Assembly로 만들어 봅니다.

잘 생성되었습니다. :)

AL.exe를 이용해서 Assembly에 리소스를 추가할 수가 있습니다. 리소스를 추가하게 되면 앞에서 살펴본 ManifestResource 테이블이 생성되겠지요.

다음과 같이 AL.exe를 이용하여 DLL을 생성합니다.
그리고 테이블을 살펴보니

ManifestResource 테이블에 kara.jpg 이미지 리소스가 추가되어 있네요!! 물론 csc.exe나 vbc.exe 같은 각 언어의 컴파일러에서도 /resource 옵션을 이용하여 리소스 추가가 가능합니다.



휴- 정신없네요. 한숨 돌립시다. @_@




우리가 생성한 test.dll 파일을 보니까 뭔가 썰렁합니다. 파일 정보를 보니 버전 리소스 정보가 하나도 없네요!
물론 Visual Studio IDE를 이용하여 프로젝트를 생성하면 C#의 경우는 AssemblyInfo.cs 라는 파일이 자동으로 추가되면서 이 파일을 통해 생성하려는 Assembly의 파일 정보를 구성할 수 있습니다. 그렇다면 AL.exe나 csc.exe, vbs.exe의 컴파일러를 이용하여 Assembly를 생성하려는 경우 파일 정보는 어떻게 추가 할 수 있을까요?

using System.Reflection;

[assembly: AssemblyTitle("Test Library")]
[assembly: AssemblyDescription("Test")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("VSTS2010")]
[assembly: AssemblyProduct("Test Library")]
[assembly: AssemblyCopyright("Copyleft ©  2010")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]


public class GoodbyeWorld
{
public static void PrintGoodbye()
{
System.Console.WriteLine("Goodbye World");
}
}

public sealed class Program
{
public static void Main()
{
System.Console.WriteLine("Hi");
GoodbyeWorld.PrintGoodbye();
}

}

위의 코드를 다음과 같이 컴파일 합니다.

그러면 생성된 test.dll 에는 파일 정보가 포함되게 됩니다.


오늘 너무 많은 것을 하네요. 하지만 다음 주 진도를 위해 몇 가지를 더 짚고 넘어가야 합니다. 앞에서 버전 번호를 보면 알겠지만 버전 번호는 다음과 같은 형식을 갖고 있습니다.

주 번호(Major) / 부 버전 번호(Minor) / 빌드 번호(Build) / 리비전 번호(Revision)

Test.dll의 버전은 1.0.0.0 이라고 되어 있습니다. 앞의 1.0은 공식적으로 사용되는 버전의 숫자입니다. 보통 .NET FrameWork 2.0 , 3.5 이런 식으로 이야기 할 때의 버전 번호입니다.

세번 째의 빌드 번호는 말 그대로 빌드 할 때마다 증가하는 번호를 의미합니다. 만약 Test.dll이 32번째 빌드된 버전이라면 2.0.32.0 으로 버전 번호가 매겨지겠지요. 마지막 번호는 빌드의 수정을 의미합니다. 만약 32번째 빌드된 버전에 문제가 있어서 그날 다시 빌드를 해야 한다면 마지막 번호가 증가를 하게 됩니다.

이 버전 구성 방식은 꼭 지켜야 하는 규칙은 아닙니다. 하지만 Microsoft를 포함한 일반적인 소프트웨어 회사에서는 위와 같은 버전 구성 방식을 사용하고 있습니다.

그런데 위의 소스에서 보면 Assembly와 관련된 버전 정보가 하나가 아님을 알 수 있습니다.

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

이 두 개 말고도 [assembly:AssemblyInformationVersion] 이라는 항목도 있습니다.  결국 Version과 관련된 항목이 3개가 있는데 CLR에서 사용하는 버전 정보는 [assembly: AssemblyVersion] 입니다. 이 버전 정보는 CLR에서 굉장히 중요합니다. 뒤에서 배우게 될 Strongly named assemblies를 바인딩 할 때 사용되는 버전 정보입니다. 이 버전 번호가 Assembly의 metadata 테이블 정보 중 AssemblyRef 테이블 항목에 기록이 됩니다.

버전 정보와 함께 Assembly는 Culture(언어) 라는 항목을 가집니다. 이 항목의 값에 따라 사용자는 영어 버전, 일본어 버전, 한국어 버전등의 Assembly를 가질 수 있게 됩니다. Culture에 들어가는 문자열은 주태그 - 부가태그의 형식으로(표준 RFC1766) 표현됩니다.

de-AT Austrian German
en-GB British English
en-US U.S. English

일반적으로는 코드가 포함된 Assembly에는 Culture를 지정하지 않습니다. 코드가 언어에 종속적으로 실행되어야 할 이유가 없기 때문이지요. 대신에 다국어를 지원하기 위한 어플리케이션을 만들 경우 Culture와 관련된 전용 리소스만을 포함한 Assembly를 만들어서 빌드를 하게 됩니다.(이를 satellite assembly - 위성 어셈블리 라고 합니다.) 이렇게 포함된 리소스는 System.Resources.ResourceManager 클래스를 이용하여 사용하게 됩니다.



오늘은 여기까지! 다음주에 계속됩니다!! :)

참고 자료
CLR Via C# 2nd.

'CLR' 카테고리의 다른 글

6. Assembly - GAC(Global Assembly Cache)  (2) 2010.02.02
5. Assembly - Strongly named assemblies  (0) 2010.01.26
3. MSCorLib & Metadata  (4) 2010.01.06
2. CLR! CLR! CLR!  (3) 2009.12.30
1. Hello 世界  (0) 2009.12.23
안녕하세요. 아직 떨린 마음을 감추지 못하는 팀블로그 새내기 박세식입니다. 너무나 오랜만에 포스팅을 하게되네요. 제 개인사까지 다 말씀드릴 필요는 없겠지만, 여러분 모두 새해에는 하시는일 모두 잘되었으면 좋겠습니다. 진심으로요ㅠㅠ 늦었지만 새해 복 많이 받으세요^^

이번 시간은 ASP.NET MVC와 인사를 나눠보는 시간을 갖도록 하겠습니다. 반갑게 만나보도록 하죠^^

M, V, C의 각방생활

먼저 프로젝트를 생성합니다. 새 프로젝트 열기에서


ASP.NET MVC 2 Web Applicatoin 을 선택하고, 이름은 HelloMVC 로 하겠습니다. OK를 클릭하면 다음과 같이 유닛 테스트 프로젝트를 생성할 것인지 묻는 창이 뜹니다. (이게 ASP.NET MVC의 장점이라는 겁니다. 프로젝트 자체에서 유닛 테스트를 지원해주고 있습니다. 이 창에서 Yes 를 선택하면 간단하게 유닛테스트 프로젝트를 생성할 수 있습니다.) 이 시간은 유닛테스트와는 전혀 상관이 없는 관계로 No를 선택하도록 하겠습니다.


그러면, 다음과 같은 구조의 프로젝트가 생성된 것을 확인하실 수 있습니다.


Controllers, Models, Views 폴더가 각각 분리되어 생성되었습니다. 지나가는 얘기로, 저의 한가지 꿈이 여러개의 방이 있는 집을 갖는건데요.^^; 하나는 서재실, 또 하나는 음악실, 나머지 하나는 침실 뭐 이런거죠. 정말 각각의 방 안에서의 할일이란 명확합니다. 각각의 방에서 할일들을 한 방에서 하게 된다면... 잠시 상상해보겠습니다. 한쪽에서는 드럼과 피아노와 일렉기타의 절묘한 하모니의 시끄러움(?)이, 다른 한쪽에서는 책과 씨름하며, 또 다른 한쪽에서는 코를 곯며 자는 모습... 상상이 가십니까? 음악실에서는 음악을 연주하며 맘껏 소리높여 노래도 부르고, 서재실에서는 조용한 가운데 책도 보며 교양을 쌓고, 침실에서는 자면되는거죠. 모델, 뷰 그리고 컨트롤러 또한 각자의 할 일이 뚜렷하기 때문에 한 방에 같이 있을 수가 없습니다. 각방을 써야하는거죠^^ 

프로젝트가 생성될때 샘플페이지도 같이 생성이 됩니다. F5를 눌러서 확인해보겠습니다. 클릭하시면 디버깅을 할 수 있도록 Web.config 파일을 수정한다는 팝업창이 뜨는데요, OK하고 넘어가도록 합니다.


자, Welcome to ASP.NET MVC! 가 출력되는 것을 확인하실 수 있습니다. 우리를 먼저 반갑게 맞이해주는데요.^^ 기분좋네요~ 가만히 있을 순 없죠. 저도 인사를 해보도록 하겠습니다.

Hello! ASP.NET MVC!!!

먼저, 컨트롤러 폴더의 HomeController 클래스의 Index() 메쏘드를 다음과 같이 수정하겠습니다.

public ActionResult Index()
{
    ViewData["Message"] = "Hello! ASP.NET MVC!!!";
    return View();
}

소스 1 - /Controllers/HomeController.cs

컨트롤러는 ViewData 를 통해서 뷰에 데이터를 전달할 수 있습니다. 뷰에서는 인라인 코드로 ViewData에 접근할 수 있습니다. (자세한건 여기에서 'ViewData로 뷰에 전달하기'를 봐주세요) 수정한 내용은 F5 로 실행하여 확인할 수 있습니다.


그런데 주소를 보니 http://localhost:1589/로 되어있는데도 원하는 결과를 확인할 수 있습니다. ASP.NET MVC는 기존 웹폼에서처럼 직접적인 페이지 호출이 아닌 라우팅 룰에 의해 호출이 됩니다. ASP.NET MVC 애플리케이션이 처음 시작되면 Global.asax.cs 에 있는 Application_Start() 메쏘드가 호출되고 이는 라우트 테이블을 생성합니다.


소스 2 - Global.asax.cs

라우트 테이블은 들어오는 웹 요청을 3부분으로 나눕니다. 처음은 컨트롤러 이름과 매핑이되고, 두번째는 액션메쏘드와 매핑이 되고, 세번째는 그 메쏘드에 전달될 파라미터와 매핑이 됩니다. 예를들어, /Product/Detail/3 과 같이 요청이 들어오면 다음과 같이 나뉩니다.

Controller  = ProductController
Acton = Detail
Id = 3

예제에서 주소가 http://localhost:1589/  이렇게 되어도 라우트 테이블의 디폴트값인 HomeController의 Index 액션메쏘드를 호출하기 때문에 우리가 원하는 결과를 볼수 있었던 거죠^^

이제 인사는 나누었는데 처음 만남에 인사만 나누기는 섭섭하죠? ^^; 커피라도 한잔하며 얘기 나눠보
는게 좋겠죠? 흠.. 이번시간은 간단히 인사만 나누고 끝내려고 했는데요. 너무 금방 헤어지면 정말 아쉬울것 같아서 더 진행하는 것이니 간단하게 해보도록 하겠습니다. 아무쪼록 저의 허접함을 탓하지 마옵소서...(저 스스로 분발하도록 하겠습니다^^;)

우리 차라도 한잔?

먼저 차한잔 마실거니까요 Index.aspx 를 수정해서 차 마시러 가보도록 하죠^^

<h2><%= Html.Encode(ViewData["Message"]) %></h2>
<br />
차라도 한잔 하실래요?
<%= Html.ActionLink("네", "../Menu/MenuForm") %>

소스 3 - /Views/Home/Index.aspx

Html.ActionLink() 메쏘드는 A 태그를 만듭니다. 링크를 MenuForm으로 하네요. 물론 실행해 보면 에러가 나겠죠. 메뉴컨트롤러와 해당 액션메쏘드가 없기 때문이죠. 그러면 컨트롤러를 생성해보도록 하겠습니다.

using HelloMVC.Models;

namespace HelloMVC.Controllers
{
    public class MenuController : Controller
    {
        //
        // GET: /Menu/
        public ActionResult MenuForm()
        {
            List<Menu> MenuList = new List<Menu>();

            MenuList.Add(new Menu { Id = 1, Name = "커피", Price = "4000" });
            MenuList.Add(new Menu { Id = 2, Name = "레몬에이드", Price = "5000" });

            return View(MenuList);   
        }
    }
}

소스 4 - /Controllers/MenuController.cs

요란한 밑줄이 보이는 Menu 클래스도 생성하겠습니다.

public class Menu
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Price { get; set; }
}

소스 5 - /Models/Menu.cs

모델 클래스의 경우 생성을 하신 후에 꼭! 꼭! 꼭! 빌드를 하셔야합니다. 그래야 아래 보이는 Add View를 하실때 원하는 모델 클래스를 선택하실 수 있습니다.

ViewResult로 MenuList 모델을 파라미터로 뷰페이지로 리턴합니다. 액션메쏘드에 아무데서나 마우스 오른쪽 버튼을 클릭하여 뷰페이지를 생성하도록 하겠습니다.


Add View를 클릭하면 다음과 같은 팝업이 뜹니다. 먼저 저희가 컨트롤러에서 메뉴를 추가한 리스트를 확인해보기 위해서 다음과 같이 세팅하도록 하겠습니다.


Create a stringly-typed view를 선택하고(뷰를 생성할때 모델과 강력한 결합을 이끌게 되면 직접적으로 모델의 애트리뷰트들을 액세스할수 있게 됩니다.), View data class 를 Menu클래스로 선택합니다(여기서 Menu 클래스가 안보이시면 빌드를 안하신거에요. 빌드하셔야죠!). 마스터 페이지의 체크를 해제하도록 합니다. Add 버튼을 클릭하시면 MenuForm 페이지가 생성되는 것을 확인하실 수 있습니다. 바로 실행을 해보시면,


잘나오네요. 그러면 소스를 조금(?) 수정하도록 하겠습니다.

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<IEnumerable<HelloMVC.Models.Menu>>" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>MenuForm</title>
</head>
<script type="text/javascript">
    var menuList = new Array();
    function Menu() {
        this.id;
        this.name;
        this.price;
        this.quantity = 0;
        this.cPrice;
    }
    function addMenu(id, name, price) {
        var menuNum = -1;
        for (var k = 0; k < menuList.length; k++) {
            if (id == menuList[k].id) {
                menuNum = k;
                break;
            }
        }
        if (menuNum == -1) {
            var mn = new Menu();
            mn.id = id;
            mn.name = name;
            mn.price = price;
            mn.quantity++;
            mn.cPrice = price;
            menuList.push(mn);
        }
        else {
            menuList[k].quantity++;
            menuList[k].cPrice = menuList[k].quantity * parseInt(menuList[k].price);
        }

        var str = "";
        var totalPrice = 0;
        for (var i = 0; i < menuList.length; i++) {
            str += menuList[i].name + " " + menuList[i].quantity + "잔 " + menuList[i].cPrice + "<br />";
            totalPrice += parseInt(menuList[i].cPrice);
        }
        document.getElementById("divMenuList").innerHTML =
str + "<br /> 돈부터 내시죠~ 여긴 선불!! " + totalPrice + "원 되겠습니다~~^^";
    }
</script>
<body>
    <h2>Menu</h2>
    <table width="400px" cellspacing="1" style="background-color:#CCCCCC">
    <% foreach (var menu in Model) { %>
    <tr>
        <td style="background-color:#EEEEEE"><%= Html.Encode(menu.Name) %></td>
        <td style="background-color:#AAAAAA" align="center"><%= Html.Encode(menu.Price) %></td>
        <td style="background-color:#BBBBBB">
<a href="javascript:addMenu('<%= menu.Id %>', '<%= menu.Name %>', '<%= menu.Price %>');"><%= Html.Encode(menu.Name)%> 추가요</a></td>
    </tr>  
    <% } %>       
    </table><br />
    =================== 주문서 ===================<br /><br />      
    <div id="divMenuList" style="border:1px solid #E3E3E3; width:400px; height:200px" ></div>
</body>
</html>

소스 6 - /Views/Menu/MenuForm.aspx

이상하게 복잡스럽게 보이지만(죄송합니다__) 수정된것은 테이블에 색깔준것과 주문추가 이벤트, 주문서네요.
결과물을 보면


추가 이벤트를 일으킬때마다 주문서가 수정되는 간단한 예제를 보고 계십니다.^^

마무리요

마지막의 결과물은 간단한데 너무 복잡하게 표현한건 아닌지 심히 걱정이 됩니다. 제 실력이 여기까지인지라..다음 포스팅때는 더 깔끔하고 세련된 코드로, 더 나아진 모습으로 찾아뵙도록 하겠습니다.


참고자료 :
http://www.techbubbles.com/aspnet/aspnet-35-mvc-application/

Welcome to Dynamic C#(11) - The Phantom of The Dynamic

C# 2010. 1. 14. 09:00 Posted by 알 수 없는 사용자
  이 포스트에서 소개해드리는 유령메서드는 정식버전에서는 없어진 개념입니다. 너무나도 복잡한 과정을 거쳐야 하기 때문에, 과정을 단순화 시키느라고 기존에 논의하던 개념들을 채택하지 않았다고 하는데요. 이 포스트는 예전에 이런 내용이 논의되었었다하는 정도로 봐주시구요. 실제 dynamic과 관계된 메서드 오버로딩 판별에 대해서는 이 글을 참조해주시기 바랍니다.

- 빙산의 일각!


사실 처음에는 그냥 단순히.. dynamic이란게 추가됐으니, '아 이거 쵸낸 신기하구나', '아 난 귀찮은 거 싫어하는데 이거 때매 이런거 편해지겠구나'하는 생각으로 접근을 했었는데요. dynamic이라는 키워드가 하나 추가되면서 생긴 많은 양의 추가사항들과 이야기들이 빙산 아래쪽으로 숨어 있었네요. 근데, 개인적인 게으름으로 아직 제대로 이야기 드리지 못하는거에 대해서 죄송하게 생각하구요-_- 일단, 원문을 한번 걸러서 약간의 창작을 보태는 수준에 불과하지만 계속해서 최선을 다해서 전달해드리고자 합니다.(저도 궁금하긴 하거든요-_-) 따쓰한 피드백!! 크하하하-_-


- 유령...뭐...?

유령 메서드(the phantom method)는 이 포스트시리즈의 바탕이 되는 Sam Ng의 포스트에서 언급이 되는 용어인데요. 컴파일러가 초기 바인딩단계에서 정적으로 해결할 수 없는 동적 바인딩을 해야하는 경우 사용하는 메서드를 말합니다. 즉, 실체는 없지만 컴파일러 내부에서 문제해결을 위해서 사용한다고 볼 수 있겠죠.

public class C
{
    public void Foo(int x)
    {
    }
   
    static void Main(string[] args)
    {
        dynamic d = 10;
        C c = new C();
        c.Foo(d);
    }
}

위와 같은 코드가 있다고 할때요, Foo의 호출을 바인드하기 위해서 컴파일러는 오버로드 판별 알고리즘을 통해서 C.Foo(int x)같이 딱들어맞는 후보메서드가 포함되어 있을 후보군을 만듭니다. 그리고 매개변수가 변환가능한지를 판단합니다. 그런데, 아직 dynamic의 변환가능성에 대해서는 이야기를 해본적이 없으므로~ 일단, 그거에 대해서 먼저 이야기 해보도록 하겠습니다.

우선, 빠르고 간단하게 정의를 내려보자면, "모든 타입은 dynamic으로 변환이 가능하고, dynamic은 어떤 타입으로도 형변환 할 수 없습니다." 말이 안된다고 생각하실 수 있습니다. 그럼 그 의문을 풀기 위해서 형변환을 고려할 수 있는 상황들을 생각해보기로 하죠.
(주석) 원문에서 "everything is convertible to dynamic, and dynamic is not convertible to anything"이라고 하고 있는데요, 조금 알아보니 Sam Ng가 이야기한 건 아마 암시적 형변환을 두고 이야기 한 것 같습니다. dynamic에서 다른 타입으로 명시적 형변환은 가능합니다. 그리고 이에 대해서 다음 포스트에서 좀 더 자세하게 말씀 드리겠습니다. 

우선, c를 정적인 타입이라고 하고, d를 동적인 타입의 표현식이라고 했을때요, 형변환에 대해서 고려해볼 수 있는 상황은 아래와 같습니다.

1. 오버로드 판별 - c.Foo(d)
2. 대입 형변환 - C c = d
3. 조건문 - if(d)
4. using절 - using(dynamic d = ..)
5. foreach - foreach(var c in d)

일단, 이번 포스트에서는 1번에 대해서 살펴보도록 하구요, 나머지는 다음기회로 미루겠습니다.


- 오버로드 판별의 경우

일단 매개변수 변환가능성으로 돌아가서 생각해보겠습니다. dynamic은 어떤 타입으로도 형변환 할 수 없기 때문에, d는 int로 형변환이 불가능합니다. 하지만, 모처럼 dynamic타입인 매개변수를 썼으니 오버로드 판별도 동적으로 일어났으면 합니다. 그래서 여기서 유령 메서드가 등장합니다.

유령메서드는 오버로드 판별의 후보군에 슬쩍 추가되는데요, 파라미터개수가 똑같고 모든 파라미터가 dynamic타입인 메서드입니다.

즉, 후보군에 Foo(int x, int y), Foo(string x, string y)가 있다면, 유령메서드는 Foo(dynamic x, dynamic y)처럼 생겼겠죠.

유령 메서드가 후보군에 끼어들게되면, 당연하겠지만, 다른 후보군 메서드와 똑같이 취급됩니다. '모든 타입은 dynamic으로 형변환이 되지만, dynamic은 어떤 타입으로도 형변환이 안된다.'는 명제를 다시 한번 떠올려 볼 때입니다. 아주 적절한 타이밍이죠. 이 말은 dynamic타입의 매개변수가 주어진 상황에서 다른 모든 오버로드 후보군은 판별을 통과하지 못하지만, 유령 메서드는 유유히 판별을 통과할 수 있다는 말입니다.

서두에서 제시했던 예제를 다시 보시면, 한개의 dynamic타입을 매개변수로 받습니다. 이 상황에서 오버로드는 두개인거죠. Foo(int)와 유령 메서드인 Foo(dynamic). 전자는 판별을 통과하지 못합니다. dynamic은 int로 형변환이 안되기 때문이죠. 하지만 후자는 성공합니다. 그래서 그 메서드에 호출을 바인드하게 되는거죠.

그리고 호출이 유령 메서드에 바인드 되면, 컴파일러는 DLR을 통해서 런타임에 호출이 제대로 이루어 지도록 조치를 취하는 거죠.

그럼, 한가지 의문이 남는데요. 유령 메서드는 언제 끼어들게 되는 걸까요?


- 유령이 끼어드는 그 순간

컴파일러가 오버로드 판별을 할때, 초기 후보군을 놓고 고심을 합니다. 메서드 호출에 dynamic 매개변수가 있다면, 컴파일러는 후보군을 찬찬히 살펴보면서 유령 메서드를 소환해야 하는지 고심합니다. 유령 메서드가 끼어드는 상황은 아래와 같습니다.

1. dynamic이 아닌 모든 매개변수는 후보군에 있는 파라미터로 형변환이 가능하다.
2. 최소한 한개 이상의 dynamic매개변수가 후보군에 있는 파라미터로 형변환이 불가능하다.

일전에, dynamic타입인 매개변수가 포함된 메서드 호출이라도 정적으로 바인드될 수 있다고 말씀을 드렸었는데요. 이 경우가 2번으로 설명이 됩니다. dynamic매개변수와 일치하는 dynamic파라미터를 갖는 후보메서드가 있다면, 호출은 그 메서드로 정적 바인딩이 됩니다.

public class C
{
    public void Foo(int x, dynamic y)
    {
    }
   
    static void Main(string[] args)
    {
        C c = new C();
        dynamic d = 10;           
        c.Foo(10, d);
    }
}

위와 같은 경우, 오버로드 후보군에 정확하게 Foo호출의 매개변수인 int와 dynamic타입에 일치하는 메서드가 있기 때문에, 정적으로 바인딩되고, dynamic lookup이 발생하지 않습니다. 즉, 오버로드 판별시에 후보군을 한번 쭉 훑었는데도 유령 메서드가 끼어들지 않았다면, 비록 dynamic타입의 매개변수가 있다고 하더라도 원래 하던대로 오버로드 판별을 진행하는 거죠.


- 유령메서드를 통한 할당과 dynamic receiver를 통한 할당은 뭐가 틀린거냐

dynamic receiver의 경우는 런타임 바인더가 실제 런타임시의 receiver의 타입을 기준으로 오버로드 판별을 하려고 합니다. 하지만 유령메서드가 끼어든 할당의 경우는 컴파일타임의 receiver의 타입을 기준으로 진행되는 거죠.

그냥 직관적으로 생각해봤을때, receiver를 컴파일타임에 알 수 있다면, 매개변수에 dynamic타입이 있다고 하더라도 오버로드 판별의 후보군은 컴파일 타임에 밝혀져야 하는 거겠죠.


- 마치면서

사실, 이 부분에 대해서 좀 더 언급할 사항들이 있습니다. 나머지 형변환 케이스에 대해서 더 나아가기 전에, dynamic의 형변환에 대한 부분과 오버로드에 대한 이야기를 좀 더 하려고 하는데요. 여러분과 제가 가진 의문이 조금씩 더 해결됐으면 하는 생각입니다.


- 참고자료

1. http://blogs.msdn.com/samng/archive/2008/11/09/dynamic-in-c-iv-the-phantom-method.aspx