posted by 방랑군 2009. 9. 15. 13:26

 역시 궁금한거 해 놓으신 게 있어서...

감사...

 읽는 건 역시 나주에 ^^;;

참조 : http://blog.naver.com/kkwangjjang/100065860275


ASP.NET에서는 페이지를 cache할수 있는 기능이 있다는 사실은 모두 알것이다.

갑자기 궁금해졌다.

이 cache가 어떻게 동작하는지...

그래서 Test를  해보기로 했따.

 

OUtputCache 지시자를 쓰면 해당 페이지가 모두 Doration 만큼 캐쉬되어 져있다가 그 이후에 한번 refresh 되어진다.

이때, cache에서 제외될 부분만 Substitution Control로 처리하면 해당 부분만 cache되지 않고 페이지에 접근할 떄 마다 새롭게 갱신된다.

 

페이지 이벤트를 모두 걸고 실행해보았다.

캐쉬되어 있는 동안에는 새로고침을 해도 OnPreInit, OnInit, OnPreLoad, OnLoad, OnPreRender 이벤트 모두 일어나지 않았다.

캐쉬가 만료될때만 해당 이벤트들이 모두 실행되고 있었다.

Substitution 컨트롤의 callback Method로 지정된 놈만 계속 실행되고 있었다.

파란 부분이 Substitution 컨트롤 부분이다. 이부분만 캐쉬되지 않고 매번 갱신 되어진다.

눈으로 바뀌는것을 확인하기 위해 각 이벤트에 날짜를 프리트 하도록 해보았다. 파란 부분만 갱신되고 있음이 확인된다.

그렇다면 웹의 메인페이지 같은곳에서도 위와 같은 기능을 사용하면 되겠구나 싶어졌다.

 

UserControl을 이용해서 웹페이지를 조각해서 해당 UserContorl만 cache를 해주면 되지 않을까..싶은 마음에 UserContorl조각들을 만들고

한개의 페이지에서 그 조각들을 가져다가 써보기로 결심했다.!

 

A, B를 C에서 가져가다 쓰는 test를 해보았다. 여러가지 case로 해보기로 했다.

aspx페이지에는 서버코드가 존재하지 않고 cs페이지의 pageLoad이벤트에서 현재 시간을 찍는 test를 했다.

 

첫번째

A, B, C모두 캐쉬모드로 선언한다.

A, B의 Duration 을 C의 Duration 보다 작게 준다.

A, B의 Duration 과 관계없이 C의 duration 에 따라 동작한다. 즉, A,B가 10초마다 갱신되도록 설정이 되어 있다고 해도 C가 15초마다 갱신되도록 설정되어 있다면, A.B도 15초마다 갱신되는 것이다.

 

두번째

A, B, C모두 캐쉬모드로 선언한다.

A, B의 Duration 을 C의 Duration 보다 크게 준다.

이때 이상한 현상이 발생하게 된다. 화면에서 A,B가 나타나지 않는 시간이 있게된다.

 

처음엔 이해 할수없는 현상이였다.ㅜㅜ

그러나 사용자 컨트롤이 없이 test했을때가 떠올랐다.

일단 캐쉬가적용되면 해당 시간동안은 Page 및 UserControl의 이벤트가 발생하지 않는다.

그런데 test코드는 onLoad 이벤트에 존재한다. 캐쉬가 적용된 기간을 살펴보자.

C(A, B를 담고 있는 page)의 기간이 A,B의 캐쉬기간보다 짧다. 따라서 C가 갱신되어도, A,B의 캐쉬 만료기간이 되지 않아서, A,B의 내부 이벤트가일어나지 않게 되는것.

따라서 해당 이벤트에서 일어나고있는 액션은 일어나지 않는다. 그리고 C는 캐쉬가 만료되어서 새로 페이지 이벤트를 일으킨다.

그사이에 시간에는 C는 A,B를 담아서 그려내었던 캐쉬가 만료되어서 페이지에 A,B에 관한 내용을 그려낼수없는것이다. (아 참으로 오묘하도다!!!)

그런데 이해 할 수 없는 부분이 있다. aspx 페이지에 onLoad event에 있던 서버코드를 코딩하면 잘나온다. (사실 이부분을 아직 잘 이해 못하겠다ㅡ.ㅡa)

aspx 페이지에 서버코드를 넣지 않고, 저렇게 쓰려면...UserControl의 어떤 이벤트에 매칭을 시켜야 하는것일까..ㅜㅜ

 

세번째

A, B 만 캐쉬모드로 선언한다.

C는 캐쉬모드로 선언하지 않는다.

이렇게 되면, 처음 페이지를 로드할때는 A,B,C가 모두 실행된다. 그렇지만 다시 페이지를 로드하게되면 C만 실행된다.

(A,B는 캐쉬모드로 선언되어져 있으므로, 페이지 이벤트가 발생하지 않는다!!!)

이렇게 되면 A,B 의 캐쉬가 만료되는 시간에만 잠깐씩 A,B의 내용을 볼수있고, 나머지 시간에는 볼수없게 되는것이다.

C는 캐쉬모드가 아니므로!

 

기억해야 할것은..

반드시 페이지에서 캐쉬모드로 선언된 사용자 컨트롤을 사용할 경우, 그 페이지를 캐쉬모드로 선언해야 한다는 것이다!

세번째 같은 오류를 줄이기 위해서라면..!!

[출처] ASP.NET 페이지 CacheTest|작성자 꽝짱

'IT > 기본에충실' 카테고리의 다른 글

Serialize  (0) 2009.10.06
값, 참조 타입  (0) 2009.09.30
[C#] pdb 파일  (0) 2009.09.15
[ASP.NET] Transfer, Redirect, Excute..  (0) 2009.09.15
기본... 여기는...  (0) 2009.09.15
posted by 방랑군 2009. 9. 15. 13:23

갑자기 매일 컴파일하고 빌드하면 dll 또는 exe 파일과 함께 만들어지는 pdb 란 확장자를 가진놈의 정체가 궁금해졌다.

요새 msdn 과 친해지려는 노력중이라, MSDN 에게 물어봤다. 요약하면 다음과 같다.

  1. PDB 는 Program DataBase 의 줄임말이다.
  2. pdb 파일에는 프로그램의 디버깅 및 프로젝트 상태 정보가 저장되어 진다.
  3. C# 및 Visual Basic 의 경우는 /debug 모드로 빌드할 때만 만들어 진다. Release 모드일때는 만들어 지지않는다. => 강제로 만들려면, /debug:pdbonlt 를 사용한다. 이것을 사용하게 되면 pdb가 생성은 되지만 디버그 정보를 사용할수 없다. (/debug:full 을 사용하여 빌드해야만 디버깅 할 수 있는 코드가 생성된다.)
  4. Visual Studio 디버거는 project.pdb 파일을 찾기 위해 EXE 또는 DLL 파일에서 PDB의 경로를 사용합니다. 디버거가 해당 위치에서 PDB 파일을 찾을 수 없거나 프로젝트를 다른 컴퓨터로 옮긴 경우와 같이 경로가 올바르지 않으면 디버거는 EXE가 포함된 경로를 검색한 다음 옵션 대화 상자에 지정된 기호 경로를 검색합니다. 이 경로는 일반적으로 기호 노드의 디버깅 폴더입니다. 디버그 대상 이진 데이터와 일치하지 않는 PDB는 디버거에서 로드할 수 없습니다.

===========출처 MSDN (http://msdn.microsoft.com/ko-kr/library/ms241903.aspx)===========

 

좀더 찾아보니, .pdb  파일들은 디버거가 바이너리에 대한 원본 소스 파일을 찾고 해당 소스 파일의 코드에 있는 중단점을 매핑하기 위해서 사용한다. 디버깅을 위해서는 반드시 pdb 파일이 있어야 함을 여기서 알 수 있다.

 

그리고 pdb파일의 종류는 매우 다양하다. 따라서 어떤 종류의 pdb 파일인지 알수있는 가장 간단한 방법은,

pdb파일을 notepad로 보면 (물론 깨져 보인다;;;) header 부분만은 깨지지 않고 보이게 된다. 물론 절대적인 꼼수이긴 하지만..ㅋㅋㅋ

 

 

[출처] C# pdb 파일|작성자 꽝짱

'IT > 기본에충실' 카테고리의 다른 글

Serialize  (0) 2009.10.06
값, 참조 타입  (0) 2009.09.30
[ASP.NET] 페이지 CacheTest  (0) 2009.09.15
[ASP.NET] Transfer, Redirect, Excute..  (0) 2009.09.15
기본... 여기는...  (0) 2009.09.15
posted by 방랑군 2009. 9. 15. 13:20

 초창기 이거 나올때 구분지어서 알았는데, 
지금 먼지 모르겠다.... 또 확실히 구분지어 이해하려면 시간이 걸린다...

머리 나빠 다시 보아서 구분을 지을려고 해서 시간이 많이 걸리기 때문에 귀찮은 것들 중에 하나다...

일단 참조로 설명 해 놓은거 놓는다...
솔직히 머리나뻐서 한번 속독해서 먼소린지 잘 모르겠다 . --;

나중에 시간 되면 정리하자....

 참조 : http://blog.naver.com/kkwangjjang/100058429953

일단 transfer를 메모해두기전에, 요놈을 공부하면서 HttpContext란놈이 또 등장했다.

HttpContext란 개념은 많이 들어보긴했지만, 웬지 생소한..그림그려봐!라고 하면 못그릴것같은 놈이다.

이놈은 서버를 통해 들어오고 나가는 클라이언트 측 요청과 응답정보를 관리하는 개체이다.

즉, 클라이언트의 요청으로 한개의 프로세스가 모두 마쳐지고 서버가 클라이언트로 응답을 보내기까지의

한개의 덩어리 라고 하면 이해가 좀 쉬워지는 놈이다.

 

자자 이제 본론으로 궈궈

  • HttpResponse.Redirect와 다른점은 Redirect는 Client에서 Http헤더를 전송해서 브라우저가 새로운 페이지를 요청하게 하는 기술이다. (302 "Object Moved"를 요청한다.)
  • 이에반해서 Transfer는 서버측 페이지 이동이다. Transfer는 페이지간 포스트백과 비슷한 면이 존재한다. (Cross-Page Postback)
    • 원본페이지의 공유멤버와 포함된 컨트롤들을 엑세스 할 수 있다.
    • PreviousPage 속성을 이용해서 원본페이지의 개체를가져올수 있다. 그러나 여기서 다른점이 존재한다.
      Cross-Page Postback에서의 Page Event발생 flow를 보면, 대상페이지가 원본페이지의 개체를 액세스 할때, 원본페이지는 PreInit -> Init -> InitComplete -> PreLoad -> Load -> LoadComplete 까지 수행되어 진다.
      정 상적인 Page의 Event Flow : PreInit -> Init -> InitComplete -> PreLoad -> Load -> LoadComplete -> PreRender -> Render
      그러나 Transfer의 경우 원본페이지는 대상페이지를 실행 한 후에도 여전히 메모리에 존재함으로써 새로 페이지객체가 생성되어 지지 않게 된다!
  • Transfer는 서버측 페이지 이동이므로, 클라이언트(브라우저)의 주소창의 내용이 갱신되지 않는다.
  • 이외에 Excute와의 다른점인 페이지 제어권이 있다.
    • Transfer는 대상페이지가 실행되면 바로 원본페이지의 실행을 멈추고 제어권을 대상페이지에게 넘긴다.
    • Execute의경우, 대상페이지가를 호출한 후 제어가 다시 원본페이지로돌아오게 된다.

기본에 충실하자는 마음으로 기본적인 내용을 보다보니, 까먹지 말아볼까~하는 내용이 눈에 들어온다.

요것도 그것중하나!

메모하는 습관^^

내용 출처 : IT Expert ASP.NET 2.0 (산지 한2년됐뜨니 Naver 책DB에도 없네;;쩝;;)

[출처] HttpServerUtility.Transfer|작성자 꽝짱


'IT > 기본에충실' 카테고리의 다른 글

Serialize  (0) 2009.10.06
값, 참조 타입  (0) 2009.09.30
[ASP.NET] 페이지 CacheTest  (0) 2009.09.15
[C#] pdb 파일  (0) 2009.09.15
기본... 여기는...  (0) 2009.09.15
posted by 방랑군 2009. 9. 15. 13:13


  분명히 봤다... 그리고 안다.. 했을때는...

그러나, 시간이 지나면 잊어버린다.... 
내가 안정적인 위치에 있지 않기 때문에 밑바닥이 튼튼한 놈으로 보여야 하는데....

중요한 순간에 잊어버린 이 기본들 때문에 의기 소침에 빠진다..... --;

그래서, 여기 순서에 상관없이 정리 해두기 위해 만들었다 ^^;;


'IT > 기본에충실' 카테고리의 다른 글

Serialize  (0) 2009.10.06
값, 참조 타입  (0) 2009.09.30
[ASP.NET] 페이지 CacheTest  (0) 2009.09.15
[C#] pdb 파일  (0) 2009.09.15
[ASP.NET] Transfer, Redirect, Excute..  (0) 2009.09.15
posted by 방랑군 2009. 9. 11. 16:17

C# 3.0 개요

Anders Hejlsberg, Mads Torgersen
March 2007

적용 대상 :
Visual C# 3.0
개요 : C# 3.0 기술 개요입니다.높은 순서로 함수 스타일의 클래스 라이브러리 작성과 사용을 지원하는 C# 2.0 기반의 언어확장을 소개합니다.

목차

소개
26.1 암시적으로 형식화된 로컬 변수
26.2 확장 메서드
26.2.1 확장 메서드 선언
26.2.2 사용 가능한 확장 메서드
26.2.3 확장 메서드 호출
26.3 lambda 식
26.3.1 익명 메서드와 lambda 식변환
26.3.2 대리자(delegate) 작성식
26.3.3 형식 유추
26.3.3.1 제 1 단계
26.3.3.2 제 2 단계
26.3.3.3 입력 형식
26.3.3.4 출력 형식
26.3.3.5 종속성
26.3.3.6 출력 형식 유추
26.3.3.7 명시적 인수 형식 유추
26.3.3.8 정확한 유추
26.3.3.9 낮은 바인딩 유추
26.3.3.10 고정
26.3.3.11 유추되는 반환 형식
26.3.3.12 메서드 그룹의 변환 형식 유추
26.3.3.13 식직합의 최적 공통 형식
26.3.4 오버로드 해결
26.4 개체 이니셜라이저와 컬렉션 이니셜라이저
26.4.1. 개체 이니셜라이저
26.4.2 컬렉션 이니셜라이저
26.5 익명형식
26.6 암시적으로 형식화된 배열
26.7 쿼리 식
26.7.1 쿼리 식 변환
26.7.1.1 연속된 select 절과 groupby 절
26.7.1.2 범위 변수의 명시적 형식
26.7.1.3 역쿼리식
26.7.1.4 from 절, let 절, where 절, join 절, orderby 절
26.7.1.5 select 절
26.7.1.6 groupby 절
26.7.1.7 투명한 식별자(dentifiers)
26.7.2 쿼리 식 패턴
26.8 식 트리
26.8.1 오버로드 해결
26.9 자동적으로 구현된 속성

소개

이 기사에는 Visual C# 3.0에 적용된 몇가지 업데이트 내용이 포함되어 있습니다. 종합적인 사양은 언어 출시 때에는 제공될 예정입니다.
C# 3.0 에서는 높은 순서의 함수 스타일의 클래스 라이브러리 작성과 사용을 지원하는 C# 2.0 을 기반으로 몇가지의 언어 확장이 도입되었습니다. 이러한 언어 확장으로 관계형 데이터베이스나 XML 등의 분야의 쿼리 언어와 동등의 표현력을 가지는 합성 API 구축이 가능합니다. 확장 기능은 다음과 같습니다.
  • 암시적으로 형식화된 로컬 변수 : 로컬 변수 초기화에 사용되는 식에서 로컬 변수의 형식을 유추할 수 있습니다.
  • 확장 메서드 : 기존의 형식이나 구축된 형식을 추가 메서드로 확장할 수 있습니다.
  • lambda 식: 익명 메서드 진화형으로, 고도의 형식 유추 및 대리자(delegate) 형식이나 식트리 변환을 제공합니다.
  • 개체 이니셜라이저:  개체 구축과 초기화가 간단합니다.
  • 익명형식 : 개체 이니셜라이저에서 자동적으로 유추되어 작성되는 튜플형입니다.
  • 암시적으로 형식화된 배열 : 배열 이니셜라이저에서 배열의 요소 형식을 유추하는 배열 작성과 초기화 형식입니다.
  • 쿼리 식 :  SQL 나 XQuery 등의 관계형 쿼리 언어 및 계층 쿼리 언어를 닮은 언어에 통합된 쿼리 용무의 구문을 제공합니다.
  • 식 트리 :  lambda 식을 코드 (대리자(delegate) 형식)가 아닌 데이터 (식 트리)로서 표현할 수 있습니다.
이 기사에서는 이러한 기능의 기술 개요를 제공합니다. 이 기사에서는「C# 언어 사양 Version 1.2」(§1 ~ §18)과 「C# 버전 2.0 사양」(§19 ~ §25)를 다루며, Visual Studio 2005 설치 디렉터리 다음과 같은 "VC#\Specifications\1041\" 디렉터리에서 이용 가능합니다.

26.1 암시적으로 형식화된 로컬 변수

암시적으로 형식화된 로컬 변수 선언에서는 선언된 로컬 변수의 형식은 그 변수 초기화에 사용되는 식에서 유추됩니다.로컬 변수 선언으로 형식으로서 var 를 지정하고 , var 라는 이름 형식이 범위 안에 없는 경우, 그 선언은 암시적으로 형식화된 로컬 변수 선언입니다.다음은 예를 나타냅니다.
var i = 5;
 var s = "Hello";
var d = 1.0;
var numbers = new int[] {1, 2, 3};
var orders = new Dictionary<int,Order>();
상기의 암시적으로 형식화된 로컬 변수 선언은 다음에 나타나는 명시적으로 형식화된 선언과 완전히 동등합니다.
int i = 5;
string s = "Hello";
double d = 1.0;
int[] numbers = new int[] {1, 2, 3};
Dictionary<int,Order> orders = new Dictionary<int,Order>();
암시적으로 형식화된 로컬 변수 선언의 로컬 변수 선언자에게는 다음과 같은 제한이 있습니다.
  • 선언자에게 이니셜라이저가 포함되어있어야 합니다.
  • 이니셜라이저는 식이어야 합니다.
  • 이니셜라이저식에는 컴파일시 형식을 지정할 필요가 있어, 이 형식은 null 형 이외의 형식이어야 합니다.
  • 로컬 변수 선언에 복수의 선언자를 포함할 수 없습니다.
  • 선언한 변수 자체를 이니셜라이저로 참조할 수 없습니다.
암시적으로 형식화된 로컬 변수 선언이 잘못된 예입니다.
var x;               // 오류.형식 유추에 사용하는 이니셜라이저가 없다.
var y = {1, 2, 3};   // 오류.컬렉션 이니셜라이저는 허가되지 않다.
var z = null;        // 오류.null 형은 허가되지 않다.
var u = x => x + 1;  // 오류.lambda 식으로 형식이 지정되지 않다.
var v = v++;         // 오류.변수 자체를 이니셜라이저로 참조할 수 없다.
로컬 변수 선언으로 형식으로서 var 를 지정해, var 라는 이름 형식이 범위 안에 있는 경우, 하위 호환성의 이유에서 그 선언은 그 형식을 참조합니다.다만, var 라는 이름 형식은 "형식 이름은 대문자로 시작한다" 는  규약에 위반하므로, 이러한 상황이 발생할 일은 거의 없을 것입니다.
for 구문 (§8.8.3)의 for-initializer 및 using 구문 (§8.13)의 resource-acquisition은 암시적으로 형식화된 로컬 변수 선언으로 할 수 있습니다. foreach 구문 (§8.8.4)의 반복 변수는 암시적으로 형식화된 로컬 변수로서 선언할 수 있습니다.이 경우, 반복 변수의 형식은 열거된 컬렉션 요소 형식으로 유추됩니다.다음은 예를 나타냅니다.
int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);
이 예에서는 n 형식은 numbers 요소 형식인 int 로 유추됩니다.
암시적으로 형식화된 로컬 변수 선언을 포함할 수 있는 것은 local-variable-declaration,for-initializer,resource-acquisition 및 foreach-statement 뿐입니다.

26.2 확장 메서드

확장 메서드는 인스턴스 메서드 구문을 사용하여 호출할 수 있는 정적 메서드입니다. 실질적으로는 확장 메서드를 사용하면, 기존의 형식이나 구축된 형식을 추가 메서드로 확장할 수 있습니다.
   확장 메서드는 인스턴스 메서드에 비해, 찾아내기 어렵고, 기능도 한정되어 있습니다. 이 때문에 확장 메서드 사용은 가능한 한 삼가해 인스턴스 메서드가 적합하지 않는 경우나 사용할 수 없는 경우에게만 사용하는 것을 추천합니다.속성, 이벤트, 연산자 등, 다른 종류의 확장 멤버에 대해서는 검토중이지만, 현재로서는 지원 되지 않습니다.

26.2.1 확장 메서드 선언

확장 메서드는 메서드의 첫번째 매개 변수에 수식자로서 키워드 this 를 지정하여 선언합니다. 확장 메서드는 non-generic 상자로 되지 않은 정적 클래스에서만 선언 가능합니다. 2개의 확장 메서드를 선언하는 정적 클래스의 예입니다.
namespace Acme.Utilities
{
   public static class Extensions
   {
      public static int ToInt32(this string s) {
         return Int32.Parse(s);
      }
      public static T[] Slice<T>(this T[] source, int index, int count) {
         if (index < 0 || count < 0 || source.Length ? index < count)
            throw new ArgumentException();
         T[] result = new T[count];
         Array.Copy(source, index, result, 0, count);
         return result;
      }
   }
}
확장 메서드의 첫번째의 매개 변수에는 this 이외의 수식자를 지정할 수 없습니다.또, 이 매개 변수의 형식을 포인터형으로 할 수 없습니다.
확장 메서드에는 일반적인 정적 메서드 기능이 모두 갖춰지고 있습니다.또, 확장 메서드는 가져오기 하면, 인스턴스 메서드 구문을 사용하여 호출할 수 있습니다.

26.2.2 사용 가능한 확장 메서드

확장 메서드는 정적 클래스내에서 선언하는지, 네임 스페이스 안에서 using-namespace-directive (§9.3.2)를 사용하여 가져오기 하면, 네임 스페이스 안에서 사용할 수 있습니다. 따라서, using-namespace-directive를 사용하면, 가져오기 한 네임 스페이스에 포함되는 형식이 가져오기 될 뿐만 아니라, 가져오기 한 네임 스페이스 안의 모든 정적 클래스의 모든 확장 메서드가 가져오기 됩니다.
실질적으로는 사용 가능한 확장 메서드는 첫번째의 매개 변수로 지정한 형식의 추가 메서드로서 다루어져 일반적인 인스턴스 메서드보다 우선도는 낮아집니다.예를 들어, 다음과 같이 using-namespace-directive를 사용하여 위의 예의 Acme.Utilities 네임 스페이스를 가져오기 했다고 합니다.
using Acme.Utilities;
이 경우, 다음과 같이 인스턴스 메서드 구문을 사용하여 정적 클래스 Extensions 내의 확장 메서드를 호출하는 것이 가능합니다.
string s = "1234";
int i = s.ToInt32();               // Extensions.ToInt32(s)와 같다
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] a = digits.Slice(4, 3);      // Extensions.Slice(digits, 4, 3)과 같다

26.2.3 확장 메서드 호출

여기에서는 확장 메서드 호출에 관한 상세한 규칙에 대해 설명합니다.다음에 나타나는 형식 메서드 호출 (§7.5.5.1)
expr . identifier ( )
expr . identifier ( args )
expr . identifier < typeargs > ( )
expr . identifier < typeargs > ( args )
의 각각에서는 일반적인 호출 처리에 의해 적절한 인스턴스 메서드가 검색 되지 않았던 경우 (구체적으로는 호출 후보가 되는 메서드 그룹가 비어 있는 경우), 구문을 확장 메서드 호출로 처리하도록 시도됩니다.우선, 메서드 호출은 각각 다음과 같은 한쪽에 고쳐 쓸 수 있습니다.
identifier ( expr )
identifier ( expr , args )
identifier < typeargs > ( expr )
identifier < typeargs > ( expr , args )
지속적으로 고쳐 쓸 수 있었던 형식은 identifier 해결 방법 이외는 정적 메서드 호출로 처리됩니다. identifier가 지정된 이름을 가지는 네임 스페이스 안의 사용 가능하고 접근 가능한 모든 확장 메서드로 구성되는 메서드 그룹에 대해서, 고쳐 쓸 수 있었던 메서드 호출의 연속된처리가 시도됩니다.이것은 외부을 둘러싸는 가장 가까운 네임 스페이스 선언에서 시작되어, 외부을 둘러싸는 각 네임 스페이스 선언으로 계속되고 그것을 포함한 컴파일 유닛으로 끝납니다.이 세트에서 적절하지 않은 메서드 (§7.4.2.1) 및 첫번째의 인수에서 첫번째의 매개 변수에의 암시적인, ID 변환, 참조의 변환 또는 박스화 변환이 존재하지 않는 메서드를 모두 제외합니다.이러한 후보 메서드, 비어 있지 않은 세트를 가져오는 최초의 메서드 그룹이 고쳐 쓸 수 있었던 메서드 호출용으로 선택되는 그룹이며, 최적인 확장 메서드를 후보세트에서 선택하기 위해 일반적인 오버로드 해결 (§7.4.2)이 적용됩니다.모든 시도가 완료해도 후보 메서드 그룹가 비어 있는 경우는 컴파일시 오류가 발생합니다.
상기의 규칙은 인스턴스 메서드가 확장 메서드보다 우선되어 안쪽의 네임 스페이스 선언으로 사용 가능한 확장 메서드가 외부의 네임 스페이스 선언으로 사용 가능한 확장 메서드보다 우선되는 것을 의미합니다.예입니다.
public static class E
{
   public static void F(this object obj, int i) { }
   public static void F(this object obj, string s) { }
}
class A { }
class B
{
   public void F(int i) { }
}
class C
{
   public void F(object obj) { }
}
class X
{
   static void Test(A a, B b, C c) {
      a.F(1);            // E.F(object, int)
      a.F("hello");      // E.F(object, string)
      b.F(1);            // B.F(int)
      b.F("hello");      // E.F(object, string)
      c.F(1);            // C.F(object)
      c.F("hello");      // C.F(object)
   }
}
이 예에서는 B 메서드는 첫번째의 확장 메서드보다 우선되어 C 메서드는 어느 쪽의 확장 메서드보다 우선됩니다.

26.3 lambda 식

C# 2.0 으로 도입된 익명 메서드를 사용하면, 대리자(delegate) 값이 예상되는 개소에서 코드 블록을 "인라인(in-line)" 기술할 수 있습니다.익명 메서드를 사용하면, 함수형 프로그램 언어의 표현력이 대부분이 제공되지만 익명 메서드 구문에는 그 구문이 약간 장황하고 명령적입니다. lambda 식을 사용하면, 보다 간결하고 기능적인 구문으로 익명 메서드를 기술할 수 있습니다.
lambda 식은 매개 변수 리스트예  계속 되는 => 토큰 및 거기에 계속 되는 식 또는 구문 블록으로 기술합니다.
expression:
    assignment
    non-assignment-expression
    
non-assignment-expression:
    conditional-expression
    lambda-expression
    query-expression
    
lambda-expression:
    (   lambda-parameter-listopt   )   =>   lambda-expression-body
    implicitly-typed-lambda-parameter   =>   lambda-expression-body
    
lambda-parameter-list:
    explicitly-typed-lambda-parameter-list
    implicitly-typed-lambda-parameter-list
    explicitly-typed-lambda-parameter-list
    explicitly-typed-lambda-parameter
    explicitly-typed-lambda-parameter-list   ,   explicitly-typed-lambda-parameter

explicitly-typed-lambda-parameter:
    parameter-modifieropt   type   identifier
    implicitly-typed-lambda-parameter-list
    implicitly-typed-lambda-parameter
    implicitly-typed-lambda-parameter-list   ,   implicitly-typed-lambda-parameter

implicitly-typed-lambda-parameter:
    identifier
    
lambda-expression-body:
    expression
    block
=> 연산자의 우선 순위는 대입 (=)과 같습니다.또, 이 연산자는 오른쪽 결합입니다.
lambda 식 매개 변수는 명시적으로 형식 지정하는 일도 암시적으로 형식 지정할 수도 있습니다.명시적으로 형식화된 매개 변수 리스트에서는 각 매개 변수의 형식은 명시적으로 지정됩니다.암시적으로 형식화된 매개 변수 리스트에서는 매개 변수의 형식은 lambda 식이 사용되는 문맥에서 유추됩니다.구체적으로는 lambda 식이 호환성이 있는 대리자(delegate) 형식에 변환될 때, 그 대리자(delegate) 형식에 의해 매개 변수의 형식이 제공됩니다 (§26.3.1).
매개 변수가암시적으로 형식화된 것 하나뿐인 lambda 식에서는 매개 변수 리스트의 괄호를 생략 할 수 있습니다.즉,
( param ) => expr
 lambda 식은 다음과 같이 축약할 수 있습니다.
param => expr
lambda 식 예를 다음에 몇가지 보여줍니다.
x => x + 1                     // 암시적인 형식 지정.expression body가 식.
x => { return x + 1; }         // 암시적인 형식 지정.expression body가 구문.
(int x) => x + 1               // 명시적 형식 지정.expression body가 식.
(int x) => { return x + 1; }   // 명시적 형식 지정.expression body가 구문.
(x, y) => x * y               // 복수의 매개 변수.
() => Console.WriteLine()      // 매개 변수 없음.
「C# 버전 2.0 사양」 §21 에 기재되어 있는 익명 메서드 사양은 lambda 식에서도 대부분 적용됩니다.. lambda 식은 다음과 같은 점을 제외하고, 기능적으로는 익명 메서드를 닮아 있습니다.
  • 익명 메서드에서는 매개 변수 리스트를 완전하게 생략하고, 모든 매개 변수 리스트의 대리자(delegate) 형식으로 변환할 수 있도록 가능합니다.
  • lambda 식에서는 매개 변수의 형식을 생략 해 유추시킬 수 있지만, 익명 메서드에서는 매개 변수의 형식을 명시적으로 지정해야 합니다.
  • lambda 식 expression body에는 식도 구문 블록도 지정할 수 있지만, 익명 메서드 expression body로 지정할 수 있는 것은 구문 블록만입니다.
  • expression body가 식의 lambda 식은 식 트리로 변환할 수 있습니다 (§26.8).

26.3.1 익명 메서드와 lambda 식변환

   이 섹션은 §21.3 에 대신합니다.
anonymous-method-expression lambda 식은 특별한 변환 규칙을 가지는 값으로 해서 분류됩니다.이러한 값에는 형식이 없습니다만, 호환성이 있는 대리자(delegate) 형식에 암시적으로 변환 가능합니다.구체적으로는 다음과 같은 조건에 해당하는 경우에 대리자(delegate) 형식 D 는 익명 메서드 또는 lambda 식 L과 호환성이 있습니다.
  • D 와 L 의 매개 변수 수가 같습니다.
  • L 가 anonymous-method-signature를 포함하지 않는 익명 메서드인 경우, D 의 매개 변수가out 매개 변수 수식자가 지정된 매개 변수 이외이면, D 는 0 개 이상의 어떠한 형식의 매개 변수에서도 가질 수 있습니다.
  • L 가 명시적으로 형식화된 매개 변수 리스트를 가지는 경우, D 의 각 매개 변수의 형식 및 수식자는 L 의 대응하는 매개 변수와 같습니다.
  • L 가 암시적으로 형식화된 매개 변수 리스트를 가지는 lambda 식 경우, D 는 ref 매개 변수 또는 out 매개 변수를 가지지 않습니다.
  • D 의 반환 형식이 void 이며, L expression body가 식의 경우, L 의 각 매개 변수에 D 의 대응하는 매개 변수의 형식이 지정되어 있으면, L expression body는 statement-expression (§8.6)으로서 허가된 유효한 식 (§7 참조)입니다.
  • D 의 반환 형식이 void 이며, L expression body가 구문 블록의 경우, L 의 각 매개 변수에 D 의 대응하는 매개 변수의 형식이 지정되어 있으면, L expression body는 return 구문이 식을 지정하고 있지 않는 유효한 구문 블록 (§8.2 참조)입니다.
  • D 의 반환 형식이 void 이외며, L expression body가 식의 경우, L 의 각 매개 변수에 D 의 대응하는 매개 변수의 형식이 지정되어 있으면, L expression body는 D 의 반환 형식에 암시적으로 변환 가능한 유효한 식 (§7 참조)입니다.
  • D 의 반환 형식이 void 이외의, L expression body가 구문 블록의 경우, L 의 각 매개 변수에 D 의 대응하는 매개 변수의 형식이 지정되어 있으면, L expression body는 각 return 구문이 D 의 반환 형식에 암시적으로 변환 가능한 식을 지정하는 도달 불가능한 끝점(endpoint)를 가지는 유효한 구문 블록 (§8.2 참조)입니다.
다음의 예에서는 형식 A 인수를 받아, 형식 R 값을 돌려주는 함수를 나타내는 제네릭 대리자(delegate) 형식 Func<A,R> 를 사용합니다.
delegate R Func<A,R>(A arg);
다음의 대입식
Func<int,int> f1 = x => x + 1;         // OK
Func<int,double> f2 = x => x + 1;      // OK
Func<double,int> f3 = x => x + 1;      // 오류
그럼, 각 lambda 식 매개 변수와 반환 형식은 lambda 식 대입처의 변수의 형식에서 결정됩니다.첫번째의 대입식에서는 lambda 식이 대리자(delegate) 형식 Func<int,int> 에 정상적으로 변환됩니다.x 에 형식 int 가 지정되어 있는 경우, x + 1 은 형식 int 에 암시적으로 변환 가능한 유효한 식이기때문입니다.같이 2 번째의 대입식에서는 lambda 식이 대리자(delegate) 형식 Func<int,double> 에 정상적으로 변환됩니다.이것은 x + 1 의 결과 (형식 int)는 형식 double 에 암시적으로 변환 가능하기 때문에입니다.그러나, 3 번째의 대입식에서는 컴파일시 오류가 됩니다.이것은 x 에 형식 double 가 지정되어 있는 경우, x + 1 의 결과 (형식 double)는 형식 int 에 암시적으로 변환할 수 없기 때문입니다.

26.3.2 대리자(delegate) 작성식

   이 섹션은 §21.10 에 대신합니다.
대리자(delegate) 작성식 (§7.5.10.3)은 확장되어 인수에는 메서드 그룹으로서 분류되는 식, 익명 메서드나 lambda 식으로서 분류되는 식 또는 대리자(delegate) 형식의 값을 지정할 수 있게 되었습니다.
new D(E)라는 형식 (D 는 delegate-type, E 는 expression)의 delegate-creation-expression 컴파일시 처리는 다음의 순서로 행해집니다.
  • E 가 메서드 그룹의 경우는 E 에서 D 에의 메서드 그룹의 변환 (§21.9)이 존재할 필요가 있어, 대리자(delegate) 작성식은 그 변환과 같은 방법으로 처리됩니다.
  • E 가 익명 메서드 또는 lambda 식 경우는 E 에서 D 에의 익명 메서드 또는 lambda 식변환 (§26.3.1)(이)가 존재할 필요가 있어, 대리자(delegate) 작성식은 그 변환과 같은 방법으로 처리됩니다.
  • E 가 대리자(delegate) 형식의 값의 경우는 E 메서드 서명이 D 와 일치하고 있을 (§21.9) 필요가 있어, 결과는 E 와 같을 호출해 리스트를 참조하는 형식 D 가 새롭게 작성된 대리자(delegate)에의 참조입니다.E 가 D 와 일치하지 않는 경우는 컴파일시 오류가 발생합니다.

26.3.3 형식 유추

   이 섹션은 §20.6.4 에 대신합니다.
형식 인수를 지정하지 않고 제네릭 메서드를 호출하면, 형식 유추 처리에 의해, 그 호출의 형식 인수의 유추가 시도됩니다.형식 유추가 존재하는 것에 의해, 제네릭 메서드 호출로, 보다 편리한 구문을 사용할 수 있게 되어, 프로그래머는 중복 하는 형식 정보를 지정하지 않아도 됩니다.예를 들어, 다음과 같이 메서드를 선언했다고 합니다.
class Chooser
{
   static Random rand = new Random();
   public static T Choose<T>(T first, T second) {
      return (rand.Next(2) == 0)? first: second;
   }
}
이 경우, 형식 인수를 명시적으로 지정하지 않아도 다음과 같이 Choose 메서드를 호출할 수 있습니다.
int i = Chooser.Choose(5, 213);               // Choose<int> 를 호출한다
string s = Chooser.Choose("foo", "bar");      // Choose<string> 를 호출한다
형식 유추에 의해, 메서드 인수에서 형식 인수가 int, string 이라고 결정됩니다.
형식 유추는 메서드 호출 (§20.9.7) 컴파일시 처리의 일환으로서 실행되어 호출의 오버로드 해결 순서 전에 행해집니다.메서드 호출로 특정의 메서드 그룹을 지정해, 메서드 호출의 일부로서 형식 인수를 지정하지 않는 경우, 그 메서드 그룹의 각 제네릭 메서드에는 형식 유추가 적용됩니다.형식 유추가 성공했을 경우, 그 후의 오버로드 해결에서는 인수의 형식을 결정하는데, 유추된 형식 인수가 사용됩니다.오버로드 해결로, 호출하는 메서드로서 제네릭 메서드가 선택되었을 경우, 유추된 형식 인수가 그 호출의 실제의 형식 인수로서 사용됩니다.특정의 메서드 형식 유추가 실패했을 경우는 그 메서드는 오버로드 해결에 관여하지 않습니다.형식 유추가 실패한 것 만으로는 컴파일시 오류는 발생하지 않습니다만, 많은 경우, 형식 유추의 실패에 의해 오버로드 해결로 적절한 메서드가 발견되지 않고, 컴파일시 오류가 발생합니다.
지정한 인수의 수가 메서드 매개 변수의 수와 다른 경우, 유추는 즉시 실패합니다.그 이외의 경우는 제네릭 메서드가 다음과 같은 서명을 가진다고 합니다.
Tr M<X1...Xn>(T1 x1 ... Tm xm)
M(e1...em) 형식 메서드 호출에서는 형식 유추의 역할은 형식 매개 변수 X1...Xn 의 각각 대해 일의의 형식 인수 S1...Sn 를 호출 M<S1...Sn>(e1...em)가 유효하게 되도록 합니다.
유추 처리중에 각 형 매개 변수 Xi 는 특정의 형식 Si 에 고정되는지, 관련지을 수 있었던 범위세트를 가져 고정되지 않습니다.범위의 각각은 어느A 형식 T 입니다.처음은 각 형변수 Xi 는 범위의 빈 세트를 가져 고정되지 않습니다.
형식 유추는 단계적에 행해집니다.각 단계에서는 전 단계의 결과에 근거해 보다 많은 형식 변수의 형식 인수의 유추가 시행됩니다.제 1 단계에서는 범위에 대한 최초의 유추가 몇개인가 행해져 제 2 단계에서는 형식 변수가 특정의 형식에 고정되어 범위가 한층 더 유추됩니다.제 2 단계는 경우에 따라서는 몇번이나 반복되어야 합니다.
   이 이후, 이 기사내에서 "대리자(delegate) 형식"  표현을 사용했을 경우, 이것에는 Expression<D>  형식 (D 는 대리자(delegate) 형식)의 형식도 포함됩니다. Expression<D> 인수와 반환 형식은 D 와 같습니다.
   형식 유추는 제네릭 메서드 호출시인 만큼 행해지는 것은 아닙니다.메서드 그룹의 변환 형식 유추에 대해서는 §26.3.3.12 가 연속된식에 대한 최적인 공통의 형식의 알아내는 방법에 대해서는 §26.3.3.13 이 설명합니다.

26.3.3.1 제 1 단계

메서드 인수 ei 의 각각 붙고,
  • ei 가 lambda 식, 익명 메서드 또는 메서드 그룹의 경우는 Ti 형의 ei 에서 명시적 인수 형식 유추 (§26.3.3.7)(을)를 합니다.
  • ei 가 lambda 식, 익명 메서드, 메서드 그룹의 경우는 Ti 형의 ei 에서 출력 형식 유추 (§26.3.3.6)를 합니다.

26.3.3.2 제 2 단계

Xj 에 "종속성" (§26.3.3.5) 하지 않고 고정되지 않은 형식 변수 Xi 가 모두 고정됩니다 (§26.3.3.10).
이러한 형식 변수가 존재하지 않는 경우, 고정되지 않은 형식 변수 Xi 가운데, 다음의 양쪽 모두에 해당하는 것은 모두 고정됩니다.
  • Xi 에 종속성하는 형식 변수 Xj 가 적어도 1 존재합니다.
  • Xi 는 비어 있지 않은 범위세트를 가지고 있습니다.
이러한 형식 변수가 존재하지 않고, 고정되지 않은 형식 변수가 아직 존재하는 경우는 형식 유추는 실패합니다.고정되지 않은 형식 변수가 그 이상 존재하지 않는 경우는 형식 유추는 성공합니다.그 이외의 경우는 대응하는 인수의 형식 Ti 를 가져, 고정되지 않은 형식 변수 Xj 가 출력 형식 (§26.3.3.4)에는 포함되지만 입력 형식 (§26.3.3.3)에는 포함되지 않은 인수 ei 모두에게 대해서, Ti 형의 ei 에 대한, 출력 형식 유추 (§26.3.3.6)를 합니다.가 제 2 단계가 반복해집니다.

26.3.3.3 입력 형식

e 가 메서드 그룹 또는 암시적으로 형식화된 lambda 식, T 가 대리자(delegate) 형식의 경우는 T 의 인수의 형식은 모두, T 형의 e 입력 형식입니다.

26.3.3.4 출력 형식

e 가 메서드 그룹, 익명 메서드, 구문 Lambda 또는 식 Lambda이며, T 가 대리자(delegate) 형식의 경우는 T 의 반환 형식은 T 형의 e 출력 형식입니다.

26.3.3.5 종속성

Tk 형의 어느A 인수 ek 에 대해서, Tk 형의 ek 입력 형식에 Xj , Tk 형의 ek 의 출력 형식에 Xi 가 있는 경우, 고정되지 않은 형식 변수 Xi 는 고정되지 않은 형식 변수 Xj 에 "직접 종속성" 합니다.
Xj 가 Xi 에 직접 종속성하는지, Xi 가 Xk 에 직접 종속성하여 Xk 가 Xj 에 종속성하는 경우, Xj 는 Xi 에 "종속성" 합니다. 따라서,"종속성하는" 은" 직접 종속하는"  전이적(transitive)이지만, 반사폐포가 아닙니다.

26.3.3.6 출력 형식 유추

T 형식 e 에서 다음과 같은 방법으로 출력 형식 유추를 합니다.
  • e 가유추되는 반환 형식이 U (§26.3.3.11)의 Lambda 또는 익명 메서드여, T 가반환 형식이 Tb 대리자(delegate) 형식인 경우, U 에서 Tb 에 대해서 낮은 바인딩 유추 (§26.3.3.9)를 합니다.
  • 상기에 해당하지 않고, e 가 메서드 그룹이며, T 가매개 변수의 형식이 T1...Tk 의 대리자(delegate) 형식이며, 형식 T1...Tk 의 e 오버로드 해결에 의해, 반환 형식이 U 의 메서드가 1 을   U 에서 Tb 에 대해서 낮은 바인딩 유추를 합니다.
  • 상기의 어느 쪽에도 해당하지 않고, e 가 U 형식의 경우, U 에서 T 에 대해서 낮은 바인딩 유추를 합니다.
  • 상기의 어느 것에도 해당하지 않는 경우, 유추는 행해지지 않습니다.

26.3.3.7 명시적 인수 형식 유추

T 형식 e에서 다음과 같은 방법으로 명시적 인수 형식 유추를 합니다.
  • e 가인수의 형식이 U1...Uk 의 명시적으로 형식화된 lambda 식또는 익명 메서드로, T 가매개 변수의 형식이 V1...Vk 의 대리자(delegate) 형식의 경우, 각 Ui 에 대해서, Ui 에서 대응하는 Vi 에 대한 정확한 유추 (§26.3.3.8)을 합니다.

26.3.3.8 정확한 유추

형식 U 에서 형식 V 에 대한 정확한 유추는 다음과 같이 행해집니다.
  • V 가 고정되지 않은 Xi 의 한개인 경우, Xi 의 범위세트에 U 가 추가됩니다.
  • 상기에 해당하지 않고, U 가 배열형 Ue[...] 가 V 가 같은 차원의 배열형 Ve[...]의 경우, Ue 에서 Ve 에 대한 정확한 유추를 합니다.
  • 상기의 어느 쪽에도 해당하지 않고, V 가 구축된 형식 C<V1...Vk> 로, U 가 구축된 형식 C<U1...Uk> 의 경우, 각 Ui 에서 대응하는 Vi 에 대한 정확한 유추를 합니다.
  • 상기의 어느 것에도 해당하지 않는 경우, 유추는 행해지지 않습니다.

26.3.3.9 낮은 바인딩 유추

형식 U 에서 형식 V 에 대한 낮은 바인딩 유추는 다음과 같이 해 행해집니다.
  • V 가고정되지 않은 Xi 의 한개인 경우, Xi 의 범위세트에 U 가 추가됩니다.
  • 상기에 해당하지 않고, U 가 배열형 Ue[...] 이 V 가 같은 차원의 배열형 Ve[...] 의 경우 또는 U 가 1 차원 배열형 Ue[] 로 V 가 IEnumerable<Ve>, ICollection<Ve>, IList<Ve> 의 몇개의 경우,
    • Ue 가 참조형인 것을 알 수 있고 있는 경우는 Ue 에서 Ve 에 대한 낮은 바인딩 유추를 합니다.
    • 그 이외의 경우는 Ue 에서 Ve 에 대한 정확한 유추를 합니다.
  • 상기의 어느 쪽에도 해당하지 않고, V 가 구축된 형식 C<V1...Vk> 이며, U 에서 C<U1...Uk> 에의 표준 암묵 변환이 존재하는 형식의 일의세트 U1...Uk 가 존재하는 경우는 각 Ui 에서 대응하는 Vi 에 대해서 정확한 유추를 합니다.
  • 상기의 어느 것에도 해당하지 않는 경우, 유추는 행해지지 않습니다.

26.3.3.10 고정

범위세트를 가지는 고정되지 않은 형식 변수 Xi 는 다음과 같이 고정됩니다.
  • "후보의 형식" Uj 세트는 처음은 Xi  범위세트에 포함되는 모든 형식직합입니다.
  • Xi 의 각 범위를 순서에 조사합니다.X 의 각 범위 U 에 대해서, U 에서 표준 암묵 변환이 존재 "하지 않는" 형식 Uj 는 모두 후보 세트에서 제외됩니다.
  • 남아 있는 후보의 형식 Uj 안에 다른 모든 후보의 형식에의 표준 암묵 변환이 존재하는 형식 V 가 존재하는 경우는 Xi 는 V 에 고정됩니다.
  • 그 이외의 경우는 형식 유추는 실패합니다.

26.3.3.11 유추되는 반환 형식

형식 유추 및 오버로드 해결을 위해 lambda 식 또는 익명 메서드인 e 유추되는 반환 형식은 다음과 같이 결정됩니다.
  • e expression body가 식의 경우는 그 식의 형식이 e 유추되는 반환 형식입니다.
  • e expression body가 구문 블록의 경우는 그 블록의 return 구문의 식직합에 최적인 공통의 형식이 있어, 그 형식이 null 형이 아니면, 그 형식이 e 유추되는 반환 형식입니다.
  • 상기의 어느 쪽에도 해당하지 않는 경우는 e 반환 형식은 유추할 수 없습니다.
lambda 식이 관련하는 형식 유추의 예로서 다음과 같은 System.Linq.Enumerable 클래스에서 선언된 Select 확장 메서드를 생각해 봅시다.
namespace System.Linq
{
   public static class Enumerable
   {
      public static IEnumerable<TResult> Select<TSource,TResult>(
         this IEnumerable<TSource> source,
         Func<TSource,TResult> selector)
      {
         foreach (TSource element in source) yield return selector(element);
      }
   }
}
using 절을 사용하여 System.Linq 네임 스페이스를 가져오기 했다고 합니다. string 형의 Name 속성을 가지는 클래스 Customer 가 있다면, 다음과 같이 Select 메서드를 사용하여 고객 리스트의 이름을 선택할 수 있습니다.
List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);
Select 의 확장 메서드 호출 (§26.2.3)은 다음과 같이 정적 메서드 호출을 고쳐 쓰는 것으로 처리됩니다.
IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);
형식 인수를 명시적으로 지정하지 않았기 때문에 형식 유추를 사용하여 형식 인수가 유추됩니다.우선, customers 인수가 source 매개 변수에 관련지을 수 있어 T 는 Customer 이다고 유추됩니다. 앞에서 말한 lambda 식 형식 유추 처리를 사용하고, c 의 형식은 Customer 이다고 유추되어 식 c.Name 가 selector 매개 변수의 반환 형식에 관련지을 수 있고, S 는 string 으로 유추됩니다.따라서, 호출은 이하와 동등합니다.
Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)
또, 결과의 형식은 IEnumerable<string> 입니다.
다음의 예는 lambda 식 형식 유추에 의해, 형식 정보가 제네릭 메서드 호출의 인수간에 어떻게 건네줄 수 있는지를 나타냅니다.다음과 같은 메서드가 있다고 합니다.
static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) {
   return f2(f1(value));
}
이 경우, 다음의 호출
double seconds = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalSeconds);
의 형식 유추는 다음과 같이 행해집니다.우선, 인수 "1:15:30" 가 value 매개 변수에 관련지을 수 있어 X 는 string 이다고 유추됩니다.첫번째의 lambda 식 매개 변수 s 의 형식은 string 이다고 유추되어 식 TimeSpan.Parse(s)가 f1 의 반환 형식에 관련지을 수 있고, Y 는 System.TimeSpan 라고 유추됩니다. 마지막으로, 2 번째의 lambda 식 매개 변수 t 의 형식은 System.TimeSpan 이다고 유추되어 식 t.TotalSeconds 가 f2 의 반환 형식에 관련지을 수 있고, Z 는 double 라고 유추됩니다.따라서, 호출의 결과의 형식은 double 입니다.

26.3.3.12 메서드 그룹의 변환 형식 유추

제네릭 메서드 호출과 같이 제네릭 메서드를 포함한 메서드 그룹 M 가 특정의 대리자(delegate) 형식 D 에 대입되는 경우에도, 형식 유추가 적용될 필요가 있습니다. 
Tr M<X1...Xn>(T1 x1 ... Tm xm)
대리자(delegate) 형식 D 에 대입된 메서드 그룹 M 가 있다면, 형식 유추의 역할은 다음 식 D 에 대입 가능하게 되도록, 
M<S1...Sn>
형식 인수 S1...Sn 를 알아내는 것입니다.
제네릭 메서드 호출로의 형식 유추의 알고리즘과는 달라, 이 경우는 인수의 expressions은 존재하지 않고, 어느A 인수의 types 뿐입니다.특히, lambda 식이 없기 때문에 복수 단계의 유추는 불필요합니다.
대신에 모든 Xi 는 고정되지 않다고 보여져 D 의 인수의 형식 Uj 각각에서 M 의 대응하는 매개 변수의 형식 Tj 에 대한 낮은 바인딩 유추를 합니다.어느 Xi 에 관해서도 범위가 발견되지 않는 경우는 형식 유추는 실패합니다.그 이외의 경우는 모든 Xi 는 대응하는 Si (형식 유추의 결과)에 고정됩니다.

26.3.3.13 식직합의 최적 공통 형식

경우에 따라서는 식직합에 대한 공통의 형식을 유추해야 합니다.특히, 암시적으로 형식화된 배열의 요소 형식 및 익명 메서드나 구문 Lambda의 반환 형식은 이 방법으로 밝혀 냅니다.
직감적으로는 e1...em 라는 연속된식이 있다면, 이 유추는 인수에 ei 를 지정해 다음의 메서드
Tr M<X>(X x1 ... X xm)
를 호출하는 것과 동등하다고 생각할 수 있습니다.
엄밀하게는 유추는 고정되지 않은 형식 변수 X 에서 시작한후, X 형의 ei 각각에서 출력 형식 유추를 해 마지막으로, X 가 고정되어 결과적으로 얻을 수 있던 형식 S 가식의 공통의 형식이 됩니다.

26.3.4 오버로드 해결

특정의 상황에 있어서는 인수 리스트내의 lambda 식이 오버로드 해결에 영향을 줍니다.정확한 규칙에 대해서는 §7.4.2.3 을 참조해 주세요.
다음의 예는 Lambda가 오버로드 해결에게 주는 영향을 나타냅니다.
class ItemList<T>: List<T>
{
   public int Sum(Func<T,int> selector) {
      int sum = 0;
      foreach (T item in this) sum += selector(item);
      return sum;
   }
   public double Sum(Func<T,double> selector) {
      double sum = 0;
      foreach (T item in this) sum += selector(item);
      return sum;
   }
}
ItemList<T> 클래스에는 2 개의 Sum 메서드가 있습니다.어느 쪽의 메서드도 selector 인수를 받습니다.이 인수는 합계하는 값을 리스트 항목에서 추출합니다.추출된 값은 int 의 경우와 double 경우가 있어, 같이 합계 결과도 int 또는 double 입니다.
Sum 메서드는 예를 들어, 주문의 명세행의 리스트에서 합계를 계산하는데 사용할 수 있습니다.
class Detail
{
   public int UnitCount;
   public double UnitPrice;
   ...
}
void ComputeSums() {
   ItemList<Detail> orderDetails = GetOrderDetails(...);
   int totalUnits = orderDetails.Sum(d => d.UnitCount);
   double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
   ...
}
lambda 식d => d.UnitCount 는 Func<Detail,int> 와 Func<Detail,double> 의 양쪽 모두와 호환성이 있으므로, 첫번째의 orderDetails.Sum 의 호출은 어느 쪽의 Sum 메서드에도 적용할 수 있습니다.그러나, 오버로드 해결에서는 첫번째의 Sum 메서드가 선택됩니다.Func<Detail,int> 에의 변환이 Func<Detail,double> 에의 변환보다 적절하기 때문에입니다.
lambda 식d => d.UnitPrice * d.UnitCount 에 의해 생성되는 것이 double 형의 값이므로, 2 번째의 orderDetails.Sum 호출은 2 번째의 Sum 메서드 밖에 적용할 수 없습니다.따라서, 이 호출에서는 오버로드 해결에 의해 2 번째의 Sum 메서드가 선택됩니다.

26.4 개체 이니셜라이저와 컬렉션 이니셜라이저

개체 작성식 (§7.5.10.1)에는 신규 작성된 개체의 멤버 또는 신규 작성된 컬렉션의 요소를 초기화하는 개체 이니셜라이저 또는 컬렉션 이니셜라이저가 포함되는 경우가 있습니다.
object-creation-expression:
    new   type   (   argument-listopt   )   object-or-collection-initializeropt 
    new   type   object-or-collection-initializer
    
object-or-collection-initializer:
    object-initializer
    collection-initializer
개체 작성식에서는 개체 이니셜라이저 또는 컬렉션 이니셜라이저가 포함되는 경우는 생성자의 인수 리스트를 생략 할 수 있습니다.생성자의 인수 리스트  생략 하는 것은 비어있슨 인수 리스트를 지정하여 동등합니다.
개체 이니셜라이저 또는 컬렉션 이니셜라이저가 포함되는 개체 작성식을 실행하려면 , 우선 인스턴스 생성자를 호출해, 다음에 개체 이니셜라이저 또는 컬렉션 이니셜라이저에 의해 지정된 멤버 또는 요소의 초기화를 실행합니다.
개체 이니셜라이저 또는 컬렉션 이니셜라이저에서는 초기화중의 개체 인스턴스를 참조할 수 없습니다.
개체 이니셜라이저와 컬렉션 이니셜라이저를 제네릭으로 정상적으로 분석 하려면 ,§20.6.5 에 기재되어 있는 애매함을 해소하는 토큰의 리스트를 } 토큰에 의해 증강해야 합니다.

26.4.한개체 이니셜라이저

개체 이니셜라이저에서는 개체의 1 이상의 필드나 속성의 값을 지정합니다.
object-initializer:
    {   member-initializer-listopt   }
    {   member-initializer-list   ,   }

member-initializer-list:
    member-initializer
    member-initializer-list   ,   member-initializer

member-initializer:
    identifier   =   initializer-value

initializer-value:
    expression
    object-or-collection-initializer
개체 이니셜라이저는{ 토큰과 } 토큰으로 둘러싸여 콤마로 단락지어진, 연속된멤버 이니셜라이저로 구성됩니다.각 멤버 이니셜라이저에서는 초기화중의 개체의 접근 가능한 필드 또는 속성의 이름을 지정해, 그 후에 등호와 식, 혹은 개체 이니셜라이저 또는 컬렉션 이니셜라이저의 한편을 지정해야 합니다. 개체 이니셜라이저에게, 같은 필드 또는 속성의 멤버 이니셜라이저를 복수 포함하면, 오류가 됩니다.개체 이니셜라이저에서는 초기화중의 신규 작성된 개체를 참조할 수 없습니다.
등호의 뒤에 식이 지정되어 있는 멤버 이니셜라이저는 필드 또는 속성에의 대입 (§7.13.1)과 같은 방법으로 처리됩니다.
등호의 뒤에 개체 이니셜라이저가 지정되어 있는 멤버 이니셜라이저는 "상자가 된 개체 이니셜라이저", 즉 인젝션 개체를 초기화합니다. 필드나 속성에 새로운 값을 대입하는 것이 아니라, 상자가 된 개체 이니셜라이저로의 대입은 필드나 속성의 멤버에의 대입으로서 다루어집니다.상자가 된 개체 이니셜라이저는 값 형식의 속성이나, 독해 전용으로 값 형식의 필드에는 적용할 수 없습니다.
등호의 뒤에 컬렉션 이니셜라이저가 지정되어 있는 멤버 이니셜라이저는 짜넣어 컬렉션을 초기화합니다.필드나 속성에 새로운 컬렉션을 대입하는 것이 아니라, 이니셜라이저로 지정한 요소가필드나 속성에 의해 참조되는 컬렉션에 추가됩니다.필드나 속성은 §26.4.2 이 설명하는 요건을 채우는 컬렉션 형식이어야 합니다.
다음의 클래스는 2 개의 좌표를 가지는 점을 나타냅니다.
public class Point
{
   int x, y;
   public int X { get { return x; } set { x = value; } }
   public int Y { get { return y; } set { y = value; } }
}
Point 인스턴스는 다음과 같이 작성해 초기화할 수 있습니다.
var a = new Point { X = 0, Y = 1 };
이것은 다음과 같은 효과를 가집니다.
var __a = new Point();
__a.X = 0;
__a.Y = 1; 
var a = __a;
__a 는 이러한 방법을 사용하지 않으면 은폐 되어 접근 할 수 없는 임시 변수입니다.다음의 클래스는 2 개의 점에서 작성되는 사각형을 나타냅니다.
public class Rectangle
{
   Point p1, p2;
   public Point P1 { get { return p1; } set { p1 = value; } }
   public Point P2 { get { return p2; } set { p2 = value; } }
}
Rectangle 인스턴스는 다음과 같이 작성해 초기화할 수 있습니다.
var r = new Rectangle {
   P1 = new Point { X = 0, Y = 1 },
   P2 = new Point { X = 2, Y = 3 }
};
이것은 다음과 같은 효과를 가집니다.
var __r = new Rectangle();
var __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
var __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2; 
var r = __r;
__r, __p1, __p2 는 이러한 방법을 사용하지 않으면 은폐 되어 접근 할 수 없는 임시 변수입니다.
Rectangle 생성자가 다음과 같이 2 개의 편입의 Point 인스턴스를 할당합니다.
public class Rectangle
{
   Point p1 = new Point();
   Point p2 = new Point();
   public Point P1 { get { return p1; } }
   public Point P2 { get { return p2; } }
}
이 경우, 새로운 인스턴스를 할당하는 대신에 다음의 구문을 사용하고, 편입의 Point 인스턴스를 초기화할 수 있습니다.
var r = new Rectangle {
   P1 = { X = 0, Y = 1 },
   P2 = { X = 2, Y = 3 }
};
이것은 다음과 같은 효과를 가집니다.
var __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
var r = __r;

26.4.2 컬렉션 이니셜라이저

컬렉션 이니셜라이저에서는 컬렉션의 요소를 지정합니다.
collection-initializer:
    {   element-initializer-list   }
    {   element-initializer-list   ,   }

element-initializer-list:
    element-initializer
    element-initializer-list   ,   element-initializer

element-initializer:
    non-assignment-expression
    {   expression-list   }
컬렉션 이니셜라이저는{ 토큰과 } 토큰으로 둘러싸여 콤마로 단락지어진, 연속된요소 이니셜라이저로 구성됩니다.각 요소 이니셜라이저에서는 초기화중의 컬렉션 개체에 추가하는 요소를 지정합니다.또, 각 요소 이니셜라이저는{ 토큰과 } 토큰으로 둘러싸여 콤마로 단락지어진, 식의 리스트로 구성됩니다.식이 한개만의 요소 이니셜라이저는 중괄호 없이 기술할 수 있지만, 그처럼 기술했을 경우는 대입식으로 할 수 없습니다.멤버 이니셜라이저와의 헷갈림을 피하기 위해서 입니다. non-assignment-expression 작성에 대해서는 §26.3 에서 설명했습니다.
컬렉션 이니셜라이저를 포함한 개체 작성식의 예입니다.
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
컬렉션 이니셜라이저가 적용된 컬렉션 개체는 System.Collections.IEnumerable 를 구현 하는 형식이어야 합니다.그렇지 않은 경우는 컴파일시 오류가 발생합니다.컬렉션 이니셜라이저는 지정된 각 요소에 대해 차례로, 요소 이니셜라이저의 식 리스트를 사용하여 대상 개체에 대해서 Add 메서드를 호출해, 각 호출에 대해서 일반적인 오버로드 해결을 적용합니다.
다음의 클래스는 이름과 전화 번호 리스트로 구성된 연락처를 나타냅니다.
public class Contact
{
   string name;
   List<string> phoneNumbers = new List<string>();
   public string Name { get { return name; } set { name = value; } }
   public List<string> PhoneNumbers { get { return phoneNumbers; } }
}
List<Contact> 는 다음과 같이 작성해 초기화할 수 있습니다.
var contacts = new List<Contact> {
   new Contact {
      Name = "Chris Smith",
      PhoneNumbers = { "206-555-0101", "425-882-8080" }
   },
   new Contact {
      Name = "Bob Harris",
      PhoneNumbers = { "650-555-0199" }
   }
};
이것은 다음과 같은 효과를 가집니다.
var contacts = new List<Contact>();
var __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
contacts.Add(__c1);
var __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
contacts.Add(__c2);
__c1 와 __c2 는 이러한 방법을 사용하지 않으면 은폐 되어 접근 할 수 없는 임시 변수입니다.

26.5 익명형식

C# 3.0 에서는 익명형식의 개체를 작성하기 위해 new 연산자를 이름이 없는 개체 이니셜라이저와 함께 사용할 수 있습니다.
primary-no-array-creation-expression:
    ...
    anonymous-object-creation-expression

anonymous-object-creation-expression:
    new   anonymous-object-initializer

anonymous-object-initializer:
    {   member-declarator-listopt   }
    {   member-declarator-list   ,   }

member-declarator-list:
    member-declarator
    member-declarator-list   ,   member-declarator

member-declarator:
    simple-name
    member-access
    identifier   =   expression
이름이 없는 개체 이니셜라이저에서는 익명형식을 선언해, 그 형식의 인스턴스를 돌려줍니다.익명형식은 object 에서 직접 상속하는 무명의 클래스형입니다.익명형식의 멤버는 그 형식의 인스턴스의 작성에 사용되는 개체 이니셜라이저에서 유추되는 일련의 읽기 속성입니다.구체적으로는
new { p1 = e1 , p2 = e2 , ... pn = en }
이름이 없는 개체 이니셜라이저에서는 다음과 같은 형식의 익명형식이 선언됩니다.
class __Anonymous1
{
   private T1 f1 ;
   private T2 f2 ;
   ...
   private Tn fn ;
   public T1 p1 { get { return f1 ; } set { f1 = value ; } }
   public T2 p2 { get { return f2 ; } set { f2 = value ; } }
   ...
   public T1 p1 { get { return f1 ; } set { f1 = value ; } }
}
각 Tx 는 대응하는 식 ex 의 형식입니다.이름이 없는 개체 이니셜라이저안의 식이 null 형이나 unsafe 형의 경우, 컴파일시 오류가 발생합니다.
익명형식의 이름은 컴파일러에 의해 자동적으로 생성되어 프로그램 텍스트내에서 참조할 수 없습니다.
같은 프로그램내의 같은 이름으로 컴파일시 형식도 같은 연속된속성을 같은 순서로 지정하는 이름이 없는 개체 이니셜라이저 2 는 같은 익명형식의 인스턴스를 생성합니다 (리플렉션 등 특정의 상황에서는 속성의 순서가 중요해서, 이 정의에는 속성의 순서가 포함됩니다).
다음은 예를 나타냅니다.
var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;
이 예에서는 p1 와 p2 는 같은 익명형식이므로, 마지막 행의 대입은 허가됩니다.
모든 속성이 동일한 경우만, 같은 익명형식의 2 개의 인스턴스가 동일해지도록, 익명형식의 Equals 메서드와 GetHashcode 메서드는 속성의 Equals 와 GetHashcode 라는 관점에서 정의됩니다..
멤버 선언자는 간이명 (§7.5.2) 또는 멤버 접근 (§7.5.4)의 형식으로 축약할 수 있습니다.이것은 "프로젝션 이니셜라이저" 로 불려 같은 이름을 사용한 속성의 선언과 속성에의 대입의 축약법입니다.구체적으로 보면,
identifier                        expr . identifier
그렇다고 하는 형식의 멤버 선언자는 각각, 이하와 완전히 동등합니다.
identifer = identifier               identifier = expr . identifier
따라서, 프로젝션 이니셜라이저에서는 identifier는 값 뿐만이 아니라, 값의 대입처의 필드나 속성도 선택합니다.직감적으로는 프로젝션 이니셜라이저는 값 뿐만이 아니라 값의 이름도 예측합니다.

26.6 암시적으로 형식화된 배열

배열 작성식 (§7.5.10.2)구문은 확장되어 암시적으로 형식화된 배열 작성식이 지원됩닌다.
array-creation-expression:
    ...
    new   [   ]   array-initializer
암시적으로 형식화된 배열 작성식에서는 배열 인스턴스의 형식은 배열 이니셜라이저로 지정된 요소에서 유추됩니다.구체적으로는 배열 이니셜라이저내의 식의 형식에 의해 형성되는 세트에는 세트에 포함되는 각 형에서 암시적으로 변환 가능한 형식이 한개만 포함되어 있을 필요가 있어, 그 형식이 null 형이 아닌 경우, 그 형식의 배열이 작성됩니다.형식이 한개만 유추할 수 없었던 경우나, 유추된 형식이 null 형의 경우는 컴파일시 오류가 발생합니다.
암시적으로 형식화된 배열 작성식의 예입니다.
var a = new[] { 1, 10, 100, 1000 };            // int[]
var b = new[] { 1, 1.5, 2, 2.5 };            // double[]
var c = new[] { "hello", null, "world” };      // string[]
var d = new[] { 1, "one", 2, "two" };         // 오류
마지막 식에서는 컴파일시 오류가 발생합니다. int 와 string 에서는 어느 쪽에도 암시적으로 변환할 수 없기 때문입니다.이 경우는 명시적으로 형식화된 배열 작성식을 사용하여야 합니다.예를 들어, 형식을 object[] 와 지정합니다.또는 요소의 한개를 공통의 기본형에 캐스트 하는 방법도 있습니다.
암시적으로 형식화된 배열 작성식을 이름이 없는 개체 이니셜라이저와 조합하고, 익명형식으로서 형식화된 데이터 구조를 작성할 수 있습니다.다음은 예를 나타냅니다.
var contacts = new[] {
   new {
      Name = "Chris Smith",
      PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
   },
   new {
      Name = "Bob Harris",
      PhoneNumbers = new[] { "650-555-0199" }
   }
};

26.7 쿼리 식

쿼리 식은 SQL 나 XQuery 등의 관계형 쿼리 언어 및 계층 쿼리 언어를 닮은 언어에 통합된 쿼리 용무의 구문을 제공합니다.
query-expression:
    from-clause   query-body

from-clause:
    from   typeopt   identifier   in   expression

query-body:
    query-body-clausesopt   select-or-group-clause   query-continuationopt

query-body-clauses:
    query-body-clause
    query-body-clauses   query-body-clause

query-body-clause:
    from-clause
    let-clause
    where-clause
    join-clause
    join-into-clause
    orderby-clause

let-clause:
    let   identifier   =   expression

where-clause:
    where   boolean-expression

join-clause:
    join   typeopt   identifier   in   expression   on   expression   equals   expression 

join-into-clause:
    join   typeopt   identifier   in   expression   on   expression   equals   expression   into   identifier

orderby-clause:
    orderby   orderings

orderings:
    ordering
    orderings   ,   ordering

ordering:
    expression    ordering-directionopt

ordering-direction:
    ascending
    descending

select-or-group-clause:
    select-clause
    group-clause

select-clause:
    select   expression

group-clause:
    group   expression   by   expression

query-continuation:
    into   identifier   query-body
query-expression non-assignment-expression (§26.3  정의 참조)에 분류됩니다.
쿼리 식은 from 절로 시작되어, select 절 또는 group 절로 끝납니다.최초의 from 절의 후에는 from 절, let 절, where 절 또는 join 절을 0 개 이상 계속할 수 있습니다.각 from 절은 특정의 순서로 사용하는 범위 변수를 제기하는 제네레이터입니다.각 let 절에서는 값을 계산해, 그 값을 나타내는 식별자(dentifiers)를 제기합니다.각 where 절은 결과에서 항목을 제외하는 필터입니다.각 join 절에서는 소스 순서의 지정된 키가 다른 순서의 키라고 비교되어 일치하는 페어를 나타냅니다.각 orderby 절에서는 지정한 조건에 따라서 항목을 늘어놓아 바꿉니다.마지막 select 절 또는 group 절에서는 범위 변수의 관점에서 결과의 형식을 지정합니다.마지막으로, into 절은 한개의 쿼리의 결과를 그 다음의 쿼리로의 제네레이터로서 취급하여, 쿼리를 "접합하는" 것에 사용할 수 있습니다.

쿼리 식에서의 애매함

쿼리 식에는 새로운 문맥 키워드 (특정의 문맥으로 특별한 의미를 가지는 식별자(dentifiers))가 몇개인가 포함되어 있습니다.이러한 새로운 문맥 키워드에는 from, join, on, equals, into, let, orderby, ascending, descending, select, group 및 by 가 있습니다.쿼리 식 내부에 기록되어 있는 일에서 이러한 식별자(dentifiers)를 키워드나 간이명이라고 해도 사용하며 생기는 애매함을 피하기 위해 이러한 식별자(dentifiers)는 쿼리 식 내부에 기록되어 있는 일에서는 항상 키워드라고 보여집니다.
따라서, 쿼리 식은 fromidentifier가 시작되어, 그 후에 ;,=,, 이외의 토큰이 계속 되는 모든 식입니다.
쿼리 식 내부에 기록되어 있는 일에서 이러한 단어를 식별자(dentifiers)로서 사용하려면, 프리픽스(Prefix) @ 를 붙이는방법이 있습니다 (§2.4.2).

26.7.1 쿼리 식 변환

C# 3.0 언어에서는 쿼리 식의 정확한 실행 시멘틱스는 지정되지 않습니다.C# 3.0 에서는 쿼리 식은 "쿼리 식 패턴" 에 따르는 메서드 호출에 변환됩니다.구체적으로는 쿼리 식은 §26.7.2 에서 설명하도록, 특정의 서명 및 결과의 형식을 가질 것으로 예상되는 Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBy 및 Cast 라는 이름의 메서드 호출에 변환됩니다.이러한 메서드는 쿼리 안의 개체의 인스턴스 메서드에서도, 개체의 외부에 있는 확장 메서드에서도 괜찮습니다.이러한 메서드는 쿼리의 실제의 실행을 구현 합니다.
쿼리 식에서 메서드 호출에의 변환은 형식 바인드나 오버로드 해결이 실행되기 전에 행해지는 구문 매핑입니다.변환은 구문적으로 올바른 것이 보장 되지만 변환에 의해 의미적으로 올바른 C# 코드가 생성되는 것은 보장 되지 않습니다.쿼리 식이 변환되면, 그 결과적으로 실행되는 메서드 호출은 일반적인 메서드 호출로 처리되지만 이것에 의해 이번은 오류가 발견되는 경우가 있습니다.예를 들어, 메서드가 존재하지 않는 경우, 인수의 형식이 잘못되어 있는 경우, 메서드가 제네릭으로 형식 유추가 실패했을 경우 등입니다.
쿼리 식은 그 이상의 분해가 불가능하게 될 때까지, 변환을 반복해 실시하여, 처리됩니다.변환은 우선도순에  각 섹션은 전의 섹션의 변환이 완전하게 실행된 것을 전제로 합니다.
특정의 종류의 변환에서는* 에 나타내지는 투명한 식별자(dentifiers)에 의해 범위 변수가 삽입됩니다.투명한 식별자(dentifiers)의 특수한 성질에 대해서는 §26.7.1.7 이 설명합니다.

26.7.1.1 연속된 select 절과 groupby 절


from ... into x ...
연속된 쿼리 식은 다음과 같이 변환됩니다.
from x in ( from ...) ...
이 이후의 섹션에서의 변환은 쿼리에 into 가 없는 것을 전제로 합니다.
다음의 예
from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }
는 다음과 같이 변환됩니다.
from g in
   from c in customers
   group c by c.Country
select new { Country = g.Key, CustCount = g.Count() }
이것은 최종적으로는 다음과 같이 변환됩니다.
customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

26.7.1.2 범위 변수의 명시적 형식

범위 변수의 형식을 명시적으로 지정하는 from 절을 다음에 나타냅니다.
from T x in e
이것은 다음과 같이 변환됩니다.
from x in ( e ) . Cast < T > ( )
범위 변수의 형식을 명시적으로 지정하는 join 절을 다음에 나타냅니다.
join T x in e on k1 equals k2
이것은 다음과 같이 변환됩니다.
join x in ( e ) . Cast < T > ( ) on k1 equals k2
이 이후의 섹션에서의 변환은 쿼리에 범위 변수의 명시적 형식이 없는 것을 전제로 합니다.
다음의 예는
from Customer c in customers
where c.City == "London"
select c
 다음과 같이 변환됩니다.
from c in customers.Cast<Customer>()
where c.City == "London"
select c
이것은 최종적으로는 다음과 같이 변환됩니다.
customers.
Cast<Customer>().
Where(c => c.City == "London")
범위 변수의 명시적 형식은 제네릭 IEnumerable<T> 인터페이스는 아니고 non-generic IEnumerable 인터페이스를 구현 하는 컬렉션을 쿼리 하는데는 편리합니다.위의 예에서는 customers 가 ArrayList 형의 경우는 이것에 해당합니다.

26.7.1.3 역쿼리식

다음의 형식의 쿼리 식이 있다고 합니다.
from x in e select x
이것은 다음과 같이 변환됩니다.
( e ) . Select ( x => x )
다음의 예는 
from c in customers
select c
다음과 같이 변환됩니다.
customers.Select(c => c)
역쿼리식은 자명대로, 소스의 요소를 선택하는 쿼리 식입니다.변환의 뒤의 단계에서는 다른 변환 스텝에 의해 제공된 역쿼리는 소스에 옮겨놓는 것으로 삭제됩니다.다만, 쿼리 식의 결과가 소스 개체 자체가 되지 않게 하는 것은 중요합니다.쿼리 식의 결과가 소스 개체 자체의 경우, 소스의 형식과 ID 가 쿼리 바탕으로 대해 시작되어 버리기때문입니다.거기서, 이 스텝에서는 소스에 대해서 Select 를 명시적으로 호출하여, 소스 코드에 직접 기술된 역쿼리를 보호합니다.이러한 메서드가 절대로 소스 개체 자체를 돌려주지 않게 하는 것은 Select 나 다른 쿼리 연산자의 구현 사람의 책임이 됩니다.

26.7.1.4 from 절, let 절, where 절, join 절, orderby 절

2 번째의 from 절의 후에 select 절이 계속 되는 쿼리 식을 다음에 나타냅니다.
from x1 in e1
from x2 in e2
select v
이것은 다음과 같이 변환됩니다.
( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v )
2 번째의 from 절의 후에 select 절 이외의 것이 계속 되는 쿼리 식을 다음에 나타냅니다.
from x1 in e1
from x2 in e2
...
이것은 다음과 같이 변환됩니다.
from * in ( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => new { x1 , x2 } )
...
let 절이 있는 쿼리 식을 다음에 나타냅니다.
from x in e
let y = f
...
이것은 다음과 같이 변환됩니다.
from * in ( e ) . Select ( x => new { x , y = f } )
...
where 절이 있는 쿼리 식을 다음에 나타냅니다.
from x in e
where f
...
이것은 다음과 같이 변환됩니다.
from x in ( e ) . Where ( x => f )
...
into 가 없는 join 절이 select 절이 후에 계속 되는 쿼리 식을 다음에 나타냅니다.
from x1 in e1
join x2 in e2 on k1 equals k2
select v
이것은 다음과 같이 변환됩니다.
( e1 ) . Join( e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => v )
into 가 없는 join 절이 select 절 이외의 것이 후에 계속 되는 쿼리 식을 다음에 나타냅니다.
from x1 in e1
join x2 in e2 on k1 equals k2 
...
이것은 다음과 같이 변환됩니다.
from * in ( e1 ) . Join(
   e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => new { x1 , x2 })
...
into 가 있는 join 절이  select 절이 후에 계속 되는 쿼리 식을 다음에 나타냅니다.
from x1 in e1
join x2 in e2 on k1 equals k2 into g
select v
이것은 다음과 같이 변환됩니다.
( e1 ) . GroupJoin( e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => v )
into 가 있는 join 절, select 절 이외의 것이 후에 계속 되는 쿼리 식을 다음에 나타냅니다.
from x1 in e1
join x2 in e2 on k1 equals k2 into g
...
이것은 다음과 같이 변환됩니다.
from * in ( e1 ) . GroupJoin(
   e2 , x1 => k1 , x2 => k2 , ( x1 , g ) => new { x1 , g })
...
orderby 절이 있는 쿼리 식을 다음에 나타냅니다.
from x in e
orderby k1 , k2 , ... , kn
...
이것은 다음과 같이 변환됩니다.
from x in ( e ) . 
OrderBy ( x => k1 ) . 
ThenBy ( x => k2 ) .
 ... . 
ThenBy ( x => kn )
...
ordering 절로 descending 방향 인디케이터(indicator)를 지정하면, OrderByDescending 또는 ThenByDescending 의 호출이 대신에 생성됩니다.
이 이후가 계속 되는 변환은 각 쿼리식에 let 절, where 절, join 절, orderby 절은 존재하지 않고, 최초의 from 절이 복수는 존재하지 않는 것을 전제로 합니다.
다음의 예는
from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }
 다음과 같이 변환됩니다.
customers.
SelectMany(c => c.Orders,
    (c,o) => new { c.Name, o.OrderID, o.Total }
)
다음의 예는
from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }
다음과 같이 변환됩니다.
from * in customers.
   SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }
이것은 최종적으로는 다음과 같이 변환됩니다.
customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })
x 는 이러한 방법을 사용하지 않으면 은폐 되어 접근 할 수 없는 컴파일러에 의해 생성된 식별자(dentifiers)입니다.
다음의 예는 
from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }
다음과 같이 변환됩니다.
from * in orders.
   Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
where t >= 1000 
select new { o.OrderID, Total = t }
이것은 최종적으로는 다음과 같이 변환됩니다.
orders.
Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) }).
Where(x => x.t >= 1000).
Select(x => new { x.o.OrderID, Total = x.t })
x 는 이러한 방법을 사용하지 않으면 은폐 되어 접근 할 수 없는 컴파일러에 의해 생성된 식별자(dentifiers)입니다.
다음의 예는 
from c in customers
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }
다음과 같이 변환됩니다.
customers.Join(orders, c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c.Name, o.OrderDate, o.Total })
다음의 예는 
from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }
다음과 같이 변환됩니다.
from * in customers.
   GroupJoin(orders, c => c.CustomerID, o => o.CustomerID,
      (c, co) => new { c, co })
let n = co.Count()
where n >= 10 
select new { c.Name, OrderCount = n }
이것은 최종적으로는 다음과 같이 변환됩니다.
customers.
GroupJoin(orders, c => c.CustomerID, o => o.CustomerID,
   (c, co) => new { c, co }).
Select(x => new { x, n = x.co.Count() }).
Where(y => y.n >= 10).
Select(y => new { y.x.c.Name, OrderCount = y.n)
x 와 y 는 이러한 방법을 사용하지 않으면 은폐 되어 접근 할 수 없는 컴파일러에 의해 생성된 식별자(dentifiers)입니다.
다음의 예는 
from o in orders
orderby o.Customer.Name, o.Total descending
select o
최종적으로는 다음과 같이 변환됩니다.
orders.
OrderBy(o => o.Customer.Name).
ThenByDescending(o => o.Total)

26.7.1.5 select 절

다음의 형식의 쿼리 식이 있다고 합니다.
from x in e select v
이것은 다음과 같이 변환됩니다.
( e ) . Select ( x => v )
다만, v 가 식별자(dentifiers) x 의 경우는 변환은 단지 다음과 같이 됩니다.
( e )
예를 들어, 다음의 예는 
from c in customers.Where(c => c.City == "London")
select c
 단지 다음과 같이 변환됩니다.
customers.Where(c => c.City == "London")

26.7.1.6 groupby 절

다음의 형식의 쿼리 식이 있다고 합니다.
from x in e group v by k
이것은 다음과 같이 변환됩니다.
( e ) . GroupBy ( x => k , x => v )
다만, v 가 식별자(dentifiers) x 의 경우는 변환은 다음과 같이 됩니다.
( e ) . GroupBy ( x => k )
다음의 예는
from c in customers
group c.Name by c.Country
다음과 같이 변환됩니다.
customers.
GroupBy(c => c.Country, c => c.Name)

26.7.1.7 투명한 식별자(dentifiers)

특정의 종류의 변환에서는* 이 나타내지는 투명한 식별자(dentifiers)에 의해 범위 변수가 삽입됩니다.투명한 식별자(dentifiers)는 정식적 언어 기능이 아니고, 단지 쿼리 식 변환 프로세스에서의 중간 스텝으로서 존재합니다.
쿼리의 변환에 의해 투명한 식별자(dentifiers)가 삽입되면, 그 후의 변환 스텝으로 투명한 식별자(dentifiers)가 lambda 식및 이름이 없는 개체 이니셜라이저에게 반영됩니다.이러한 문맥으로, 투명한 식별자(dentifiers)는 다음과 같이 동작합니다.
  • 투명한 식별자(dentifiers)가 lambda 식 매개 변수로서 존재하는 경우, 관련하는 익명형식의 멤버는 자동적으로 lambda 식expression body안의 범위에 들어갑니다.
  • 투명한 식별자(dentifiers)를 가지는 멤버가 범위 안에 있는 경우, 그 멤버도 범위 안에 있는 것이 됩니다.
  • 투명한 식별자(dentifiers)가 이름이 없는 개체 이니셜라이저의 멤버 선언자로서 존재하는 경우, 투명한 식별자(dentifiers)를 가지는 멤버가 제공됩니다.
다음의 예는
from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }
다음과 같이 변환됩니다.
from * in
   from c in customers
   from o in c.Orders
   select new { c, o }
orderby o.Total descending
select new { c.Name, o.Total }
이것은 또한 다음과 같이 변환됩니다.
customers.
SelectMany(c => c.Orders.Select(o => new { c, o })).
OrderByDescending(* => o.Total).
Select(* => new { c.Name, o.Total })
이것은 투명한 식별자(dentifiers)가 소거되면, 이하와 동등합니다.
customers.
SelectMany(c => c.Orders.Select(o => new { c, o })).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.Total })
x 는 이러한 방법을 사용하지 않으면 은폐 되어 접근 할 수 없는 컴파일러에 의해 생성된 식별자(dentifiers)입니다.
다음의 예는
from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }
다음과 같이 변환됩니다.
from * in
   from * in
      from * in
         from c in customers
         join o in orders o c.CustomerID equals o.CustomerID
         select new { c, o }
      join d in details on o.OrderID equals d.OrderID
      select new { *, d }
   join p in products on d.ProductID equals p.ProductID
   select new { *, p }
select new { c.Name, o.OrderDate, p.ProductName }
이것은 또한 다음과 같이 분해됩니다.
customers.
Join(orders, c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c, o }).
Join(details, * => o.OrderID, d => d.OrderID,
   (*, d) => new { *, d }).
Join(products, * => d.ProductID, p => p.ProductID,
   (*, p) => new { *, p }).
Select(* => new { c.Name, o.OrderDate, p.ProductName })
이것은 최종적으로는 다음과 같이 변환됩니다.
customers.
Join(orders, c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c, o }).
Join(details, x => x.o.OrderID, d => d.OrderID,
   (x, d) => new { x, d }).
Join(products, y => y.d.ProductID, p => p.ProductID,
   (y, p) => new { y, p }).
Select(z => new { z.y.x.c.Name, z.y.x.o.OrderDate, z.p.ProductName })
x, y, z 는 이러한 방법을 사용하지 않으면 은폐 되어 접근 할 수 없는 컴파일러에 의해 생성된 식별자(dentifiers)입니다.

26.7.2 쿼리 식 패턴

쿼리 식 패턴에서는 쿼리 식을 지원 하기 위해 형식이 구현 할 수 있는 메서드 패턴이 규정됩니다..쿼리 식은 구문 매핑에 의해 메서드 호출에 변환되므로, 쿼리 식 패턴의 구현 방법에 관해서, 형식은 매우 유연합니다.예를 들어, 인스턴스 메서드도 확장 메서드도 호출 구문이 같아서, 패턴의 메서드는 인스턴스 메서드와 확장 메서드의 어느 쪽이라고 해도 구현 할 수 있습니다.또, lambda 식은 대리자(delegate)에도 식 트리에도 변환 가능해서, 메서드는 대리자(delegate)와 식 트리의 어느쪽이나 요청 할 수 있습니다.
쿼리 식 패턴을 지원하는 제네릭형 C<T> 의 추천 되는 형식을 다음에 나타냅니다.매개 변수와 결과의 형식과의 적절한 관계를 나타내기 위해 제네릭형을 사용하지만, non-generic형 패턴을 구현 하는 것도 가능합니다.
delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);
class C
{
   public C<T> Cast<T>();
}
class C<T>
{
   public C<T> Where(Func<T,bool> predicate);
   public C<U> Select<U>(Func<T,U> selector);
   public C<U> SelectMany<U,V>(Func<T,C<U>> selector,
      Func<T,U,V> resultSelector);
   public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
      Func<U,K> innerKeySelector, Func<T,U,V> resultSelector);
   public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
      Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector);
   public O<T> OrderBy<K>(Func<T,K> keySelector);
   public O<T> OrderByDescending<K>(Func<T,K> keySelector);
   public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector);
   public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
      Func<T,E> elementSelector);
}
class O<T> : C<T>
{
   public O<T> ThenBy<K>(Func<T,K> keySelector);
   public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}
class G<K,T> : C<T>
{
   public K Key { get; }
}
상기의 메서드에서는 제네릭 대리자(delegate) 형식의 Func<T1, R> 와 Func<T1, T2, R> 를 사용하지만 , 매개 변수나 결과의 형식의 관계가 같으면, 다른 대리자(delegate) 형식 또는 식 트리형도 이와 같이 사용할 수 있습니다.
C<T> 와 O<T> 와의 추천 되는 관계에서는 ThenBy 메서드와 ThenByDescending 메서드는 OrderBy 또는 OrderByDescending 의 결과에 대해서만 사용 가능하다는 것에 주의해 주세요. 또, GroupBy 의 결과의 추천 되는 형식에도 주의해 주세요. 각각의 내부 순서는 추가의 Key 속성을 가집니다.
표준 쿼리 연산자 (다른 시방서로 설명)를 사용하면, System.Collections.Generic.IEnumerable<T> 인터페이스를 구현하는 모든 형식에 쿼리 연산자 패턴을 구현 할 수 있습니다.

26.8 식 트리

식 트리를 사용하면, lambda 식을 실행 가능 코드는 아니고 데이터 구조로서 표현할 수 있습니다.대리자(delegate) 형식 D 에 변환 가능한 lambda 식은 System.Query.Expression<D> 형식 트리에도 변환 가능합니다.lambda 식을 대리자(delegate) 형식으로 변환하면, 실행 가능 코드가 생성되어 대리자(delegate)에 의해 그 코드가 참조되지만 식 트리형으로 변환하면, 식 트리의 인스턴스를 작성하는 코드가 생성됩니다.식 트리는 lambda 식 효율적인 메모리내 데이터 표현으로식의 구조를 알기 쉽고 명백하게 합니다.
다음과 같은 예는 실행 가능 코드로서의 lambda 식와 식 트리로서의 lambda 식을 나타냅니다. Func<int,int> 에의 변환이 존재하므로, Expression<Func<int,int>> 에의 변환도 존재합니다.
Func<int,int> f = x => x + 1;                  // 코드
Expression<Func<int,int>> e = x => x + 1;      // 데이터
이러한 대입에 따라서, 대리자(delegate) f 는 x + 1 을 돌려주는 메서드를 참조해, 식 트리 e 는 식 x + 1 을 표현하는 데이터 구조를 참조합니다.

26.8.1 오버로드 해결

오버로드 해결을 위해 Expression<D> 형에 관해서 특별한 규칙이 있습니다.구체적으로는 적절함의 정의에 다음과 같은 규칙이 더해집니다.
  • D1 가 D2 보다 적절하며  Expression<D1>는 Expression<D2> 보다 적절하고, Expression<D1> 가 Expression<D2> 보다 적절한 것은 그 경우에 한정합니다.
   Expression<D> 와 대리자(delegate) 형식과의 사이에는 적절함의 규칙은 없습니다.

26.9 자동적으로 구현된 속성

많은 경우, 속성은 다음과 같은 예와 같이 지원 필드 간단한 사용에 의해 구현 됩니다.
public Class Point {
   private int x;
   private int y;
   public int X { get { return x; } set { x = value; } }
   public int Y { get { return y; } set { y = value; } }
}
자동적으로 구현된 (자동 구현) 속성을 사용하면, 이 패턴이 자동화됩니다.구체적으로는 비추상 속성 선언으로 세미콜론 접근자 expression body를 보관 유지할 수 있습니다.양쪽 모두의 접근자가 존재할 필요가 있어, 양쪽 모두에 세미콜론 expression body가 필요합니다만, 각각 다른 필요 옵션 수식자를 지정할 수 있습니다.속성을 이와 같이 지정하면, 속성의 지원 필드가 자동적으로 생성되어 그 지원 필드에의 독해나 기입을 실시하는 접근자가 구현 됩니다. 지원 필드 이름은 컴파일러에 의해 생성되어 사용자는 접근 할 수 없습니다.
다음과 같은 선언은 위의 예와 동등합니다.
 public Class Point {
   public int X { get; set; }
   public int Y { get; set; }
}
지원 필드에는 접근 할 수 없기 때문에 지원 필드에의 독해나 기입은 속성 접근자를 통해서 실시해야 합니다.즉, 독해 전용이나 써 전용의 자동 구현 속성은 의미가 없기 때문에 허가되지 않는 것을 의미합니다.다만, 각 접근자에 다른 접근 수준을 설정하는 것은 가능합니다.따라서, 다음과 같이 개인 지원 필드가 가지는 독해 전용 속성 효과를 가져올수 있습니다.
Public class ReadOnlyPoint {
   public int X { get; private set; }
   public int Y { get; private set; }
   public ReadOnlyPoint(int x, int y) { X = x; Y = y; }
}
이 제한은 자동 구현의 속성에 의한, 구조 형식의 명시적 대입은 구조의 표준 생성자를 사용하여서만 실현될 수 있는 것도 의미합니다.이것은 속성 자체에의 대입을 실시하려면, 구조가 명시적으로 대입되어 있어야 합니다.

'IT > C#' 카테고리의 다른 글

C#2.0 Iterators,Partial 클래스,Nullable 타입  (0) 2009.10.07
닷넷 트랜잭션 정리  (0) 2009.09.30
C#2.0 , C# 3.0 New Features  (0) 2009.09.15
[C# 2.0] Generics, Iterator, Anonymous, Partial  (0) 2009.08.24
[.net Framework] System.Activator 객체  (0) 2009.08.24
posted by 방랑군 2009. 9. 8. 16:45

        private string AppConfigRead(string keyName)

        {

            string strReturn;

            Configuration currentConfig =

                ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

 

            if (currentConfig.AppSettings.Settings.AllKeys.Contains(keyName))

                strReturn = currentConfig.AppSettings.Settings[keyName].Value;

            else

                strReturn = ""; //키가없으면.

 

            return strReturn;

        }

 

        private bool AppConfigWrite(string keyName, string value)

        {

            Configuration currentConfig =

                ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

 

            if (currentConfig.AppSettings.Settings.AllKeys.Contains(keyName)) //키가 있으면

                currentConfig.AppSettings.Settings[keyName].Value = value;

            else       //키가 없으면

                currentConfig.AppSettings.Settings.Add(keyName, value);

           

            currentConfig.Save();

            ConfigurationManager.RefreshSection("appSettings");   // 내용 갱신             

            return true;

        }

posted by 방랑군 2009. 9. 8. 10:47

출처 : http://www.hoons.kr/board.aspx?Name=cshaptip&Mode=2&BoardIdx=428&Key=&Value=

Constants

VK_LBUTTON (01)
Left mouse button

VK_RBUTTON (02)
Right mouse button

VK_CANCEL (03)
Control-break processing

VK_MBUTTON (04)
Middle mouse button (three-button mouse)

VK_XBUTTON1 (05)
Windows 2000/XP: X1 mouse button

VK_XBUTTON2 (06)
Windows 2000/XP: X2 mouse button

- (07)
Undefined

VK_BACK (08)
BACKSPACE key

VK_TAB (09)
TAB key

- (0A-0B)
Reserved

VK_CLEAR (0C)
CLEAR key

VK_RETURN (0D)
ENTER key

- (0E-0F)
Undefined

VK_SHIFT (10)
SHIFT key

VK_CONTROL (11)
CTRL key

VK_MENU (12)
ALT key

VK_PAUSE (13)
PAUSE key

VK_CAPITAL (14)
CAPS LOCK key

VK_KANA (15)
Input Method Editor (IME) Kana mode

VK_HANGUEL (15)
IME Hanguel mode (maintained for compatibility; use VK_HANGUL)

VK_HANGUL (15)
IME Hangul mode

- (16)
Undefined

VK_JUNJA (17)
IME Junja mode

VK_FINAL (18)
IME final mode

VK_HANJA (19)
IME Hanja mode

VK_KANJI (19)
IME Kanji mode

- (1A)
Undefined

VK_ESCAPE (1B)
ESC key

VK_CONVERT (1C)
IME convert

VK_NONCONVERT (1D)
IME nonconvert

VK_ACCEPT (1E)
IME accept

VK_MODECHANGE (1F)
IME mode change request

VK_SPACE (20)
SPACEBAR

VK_PRIOR (21)
PAGE UP key

VK_NEXT (22)
PAGE DOWN key

VK_END (23)
END key

VK_HOME (24)
HOME key

VK_LEFT (25)
LEFT ARROW key

VK_UP (26)
UP ARROW key

VK_RIGHT (27)
RIGHT ARROW key

VK_DOWN (28)
DOWN ARROW key

VK_SELECT (29)
SELECT key

VK_PRINT (2A)
PRINT key

VK_EXECUTE (2B)
EXECUTE key

VK_SNAPSHOT (2C)
PRINT SCREEN key

VK_INSERT (2D)
INS key

VK_DELETE (2E)
DEL key

VK_HELP (2F)
HELP key

 (30)
0 key

 (31)
1 key

 (32)
2 key

 (33)
3 key

 (34)
4 key

 (35)
5 key

 (36)
6 key

 (37)
7 key

 (38)
8 key

 (39)
9 key

- (3A-40)
Undefined

 (41)
A key

 (42)
B key

 (43)
C key

 (44)
D key

 (45)
E key

 (46)
F key

 (47)
G key

 (48)
H key

 (49)
I key

 (4A)
J key

 (4B)
K key

 (4C)
L key

 (4D)
M key

 (4E)
N key

 (4F)
O key

 (50)
P key

 (51)
Q key

 (52)
R key

 (53)
S key

 (54)
T key

 (55)
U key

 (56)
V key

 (57)
W key

 (58)
X key

 (59)
Y key

 (5A)
Z key

VK_LWIN (5B)
Left Windows key (Microsoft?Natural?keyboard)

VK_RWIN (5C)
Right Windows key (Natural keyboard)

VK_APPS (5D)
Applications key (Natural keyboard)

- (5E)
Reserved

VK_SLEEP (5F)
Computer Sleep key

VK_NUMPAD0 (60)
Numeric keypad 0 key

VK_NUMPAD1 (61)
Numeric keypad 1 key

VK_NUMPAD2 (62)
Numeric keypad 2 key

VK_NUMPAD3 (63)
Numeric keypad 3 key

VK_NUMPAD4 (64)
Numeric keypad 4 key

VK_NUMPAD5 (65)
Numeric keypad 5 key

VK_NUMPAD6 (66)
Numeric keypad 6 key

VK_NUMPAD7 (67)
Numeric keypad 7 key

VK_NUMPAD8 (68)
Numeric keypad 8 key

VK_NUMPAD9 (69)
Numeric keypad 9 key

VK_MULTIPLY (6A)
Multiply key

VK_ADD (6B)
Add key

VK_SEPARATOR (6C)
Separator key

VK_SUBTRACT (6D)
Subtract key

VK_DECIMAL (6E)
Decimal key

VK_DIVIDE (6F)
Divide key

VK_F1 (70)
F1 key

VK_F2 (71)
F2 key

VK_F3 (72)
F3 key

VK_F4 (73)
F4 key

VK_F5 (74)
F5 key

VK_F6 (75)
F6 key

VK_F7 (76)
F7 key

VK_F8 (77)
F8 key

VK_F9 (78)
F9 key

VK_F10 (79)
F10 key

VK_F11 (7A)
F11 key

VK_F12 (7B)
F12 key

VK_F13 (7C)
F13 key

VK_F14 (7D)
F14 key

VK_F15 (7E)
F15 key

VK_F16 (7F)
F16 key

VK_F17 (80H)
F17 key

VK_F18 (81H)
F18 key

VK_F19 (82H)
F19 key

VK_F20 (83H)
F20 key

VK_F21 (84H)
F21 key

VK_F22 (85H)
F22 key

VK_F23 (86H)
F23 key

VK_F24 (87H)
F24 key

- (88-8F)
Unassigned

VK_NUMLOCK (90)
NUM LOCK key

VK_SCROLL (91)
SCROLL LOCK key

 (92-96)
OEM specific

- (97-9F)
Unassigned

VK_LSHIFT (A0)
Left SHIFT key

VK_RSHIFT (A1)
Right SHIFT key

VK_LCONTROL (A2)
Left CONTROL key

VK_RCONTROL (A3)
Right CONTROL key

VK_LMENU (A4)
Left MENU key

VK_RMENU (A5)
Right MENU key

VK_BROWSER_BACK (A6)
Windows 2000/XP: Browser Back key

VK_BROWSER_FORWARD (A7)
Windows 2000/XP: Browser Forward key

VK_BROWSER_REFRESH (A8)
Windows 2000/XP: Browser Refresh key

VK_BROWSER_STOP (A9)
Windows 2000/XP: Browser Stop key

VK_BROWSER_SEARCH (AA)
Windows 2000/XP: Browser Search key

VK_BROWSER_FAVORITES (AB)
Windows 2000/XP: Browser Favorites key

VK_BROWSER_HOME (AC)
Windows 2000/XP: Browser Start and Home key

VK_VOLUME_MUTE (AD)
Windows 2000/XP: Volume Mute key

VK_VOLUME_DOWN (AE)
Windows 2000/XP: Volume Down key

VK_VOLUME_UP (AF)
Windows 2000/XP: Volume Up key

VK_MEDIA_NEXT_TRACK (B0)
Windows 2000/XP: Next Track key

VK_MEDIA_PREV_TRACK (B1)
Windows 2000/XP: Previous Track key

VK_MEDIA_STOP (B2)
Windows 2000/XP: Stop Media key

VK_MEDIA_PLAY_PAUSE (B3)
Windows 2000/XP: Play/Pause Media key

VK_LAUNCH_MAIL (B4)
Windows 2000/XP: Start Mail key

VK_LAUNCH_MEDIA_SELECT (B5)
Windows 2000/XP: Select Media key

VK_LAUNCH_APP1 (B6)
Windows 2000/XP: Start Application 1 key

VK_LAUNCH_APP2 (B7)
Windows 2000/XP: Start Application 2 key

- (B8-B9)
Reserved

VK_OEM_1 (BA)
Used for miscellaneous characters; it can vary by keyboard.

Windows 2000/XP: For the US standard keyboard, the '''';:'''' key

VK_OEM_PLUS (BB)
Windows 2000/XP: For any country/region, the ''''+'''' key

VK_OEM_COMMA (BC)
Windows 2000/XP: For any country/region, the '''','''' key

VK_OEM_MINUS (BD)
Windows 2000/XP: For any country/region, the ''''-'''' key

VK_OEM_PERIOD (BE)
Windows 2000/XP: For any country/region, the ''''.'''' key

VK_OEM_2 (BF)
Used for miscellaneous characters; it can vary by keyboard.

Windows 2000/XP: For the US standard keyboard, the ''''/?'''' key

VK_OEM_3 (C0)
Used for miscellaneous characters; it can vary by keyboard.

Windows 2000/XP: For the US standard keyboard, the ''''`~'''' key

- (C1-D7)
Reserved

- (D8-DA)
Unassigned

VK_OEM_4 (DB)
Used for miscellaneous characters; it can vary by keyboard.

Windows 2000/XP: For the US standard keyboard, the ''''[{'''' key

VK_OEM_5 (DC)
Used for miscellaneous characters; it can vary by keyboard.

Windows 2000/XP: For the US standard keyboard, the ''''\|'''' key

VK_OEM_6 (DD)
Used for miscellaneous characters; it can vary by keyboard.

Windows 2000/XP: For the US standard keyboard, the '''']}'''' key

VK_OEM_7 (DE)
Used for miscellaneous characters; it can vary by keyboard.

Windows 2000/XP: For the US standard keyboard, the ''''single-quote/double-quote'''' key

VK_OEM_8 (DF)
Used for miscellaneous characters; it can vary by keyboard.

- (E0)
Reserved

 (E1)
OEM specific

VK_OEM_102 (E2)
Windows 2000/XP: Either the angle bracket key or the backslash key on the RT 102-key keyboard

 (E3-E4)
OEM specific

VK_PROCESSKEY (E5)
Windows 95/98/Me, Windows NT 4.0, Windows 2000/XP: IME PROCESS key

 (E6)
OEM specific

VK_PACKET (E7)
Windows 2000/XP: Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP

- (E8)
Unassigned

 (E9-F5)
OEM specific

VK_ATTN (F6)
Attn key

VK_CRSEL (F7)
CrSel key

VK_EXSEL (F8)
ExSel key

VK_EREOF (F9)
Erase EOF key

VK_PLAY (FA)
Play key

VK_ZOOM (FB)
Zoom key

VK_NONAME (FC)
Reserved for future use

VK_PA1 (FD)
PA1 key

VK_OEM_CLEAR (FE)
Clear key 
posted by 방랑군 2009. 9. 8. 10:36

Richard Ersek 및 Ken Jones

2개의 UI를 하나로

Microsoft의 1998 Professional Developer's Conference에서 COM+의 초기 테스트 버전이 소개되던 날 저녁에 우리는 당황한 한 프로그래머를 만났습니다. "도대체 Microsoft는 무슨 일을 하고 있습니까?" 그가 질문했습니다. "우리 일을 못하게 할 작정입니까?" 특히 6000명이 넘는 개발자를 대상으로 하는 4일간의 컨퍼런스에서 이 질문은 기묘하게 생각되었습니다. 사실은 아직도 의아하게 생각하고 있습니다. "왜 그렇게 말합니까?" "글쎄요," 그는 계속 말했습니다. "새 COM+ UI의 경우만 봐도 그래요. 시스템 관리자가 모든 일을 할 수 있기 때문에 우리와 같은 사람은 더 이상 필요 없게 될 것입니다."

우리는 그 신사에게 그의 직업은 안전하다고 설득했습니다. 사실 그는 Windows DNA 개발자였으며 아마 이전보다 더 가치 있는 일을 하고 있을 것입니다. 우리는 여러분과 같은 시스템 관리자에게도 똑같은 확신을 심어주기 위해 이 기사를 쓰게 되었습니다. 새 COM+ UI, 더 공식적으로 말하자면 구성 요소 서비스 관리 도구는 처음 보면 압도감을 느낄 수 있습니다. 구성 요소 수준 이하나 구성 요소의 인터페이스 및 메서드 수준 이상의 특성 설정을 포함하여 할 수 있는 일이 너무 많습니다.

그러나 구성 요소 서비스 관리 도구는 이중 기능을 고려할 때 그다지 위협적이지 않으며 휠씬 다양합니다. 시스템 관리자와 응용 프로그램 개발자가 함께 사용할 수 있는 UI로 설계되었습니다. 여기에서는 이 도구의 관리 기능을 소개하고 시스템 관리자가 이 도구를 사용하면 얼마나 편리한지 간단히 보여 주겠습니다. 이 기사는 개요를 다루기 때문에 자세한 과정은 소개하지 않고 개념만 설명하겠습니다. (이 과정에 대한 자세한 내용은 구성 요소 서비스 관리 도움말을 참고하십시오.)


현재 브라우저에서 인라인 프레임을 지원하지 않을 경우 여기를 누르면 별도의 페이지에서 볼 수 있습니다.

먼저 MTS와 COM+의 몇 가지 주요 변경부터 살펴보겠습니다. 이어서 가장 일반적인 다음 세 가지 관리 작업에 구성 요소 서비스 관리 도구를 어떻게 사용하는지 설명하겠습니다.

  • 응용 프로그램 구축
  • 역할 기반 보안 설정 및 응용 프로그램의 보안 식별
  • 최적의 시스템 성능을 위한 개체 풀링 관리

MTS에서 COM+로

많은 IIS 사용자가 이미 MTS(Microsoft Transaction Server)와 MTS의 UI인 MTS Explorer에 익숙해 있습니다. COM+는 Windows 2000 시스템에서 전통적인 COM과 MTS를 결합하는 서비스로 생각할 수 있습니다. COM+의 도입과 더불어 MTS의 기능이 운영 체제로 통합되어 왔습니다. 나중에 설명하겠지만 COM+는 MTS와 함께 사용할 수 있는 서비스를 확장하고 개선합니다.

MTS와 MTS Explorer를 사용하고 있는 사람은 구성 요소 서비스 관리 도구를 시작할 때 몇 가지 중요한 변경 사실을 발견할 것입니다. 특히 MTS 패키지를 이제는 COM+ 응용 프로그램으로 부릅니다.

COM 응용 프로그램의 개념이 전혀 새로운 것은 아닙니다. 단순히 함께 작업할 수 있도록 개발된 COM 구성 요소 그룹을 언급할 때 사용되는 용어입니다. 전통적인 COM 응용 프로그램에서는 구성 요소가 실행되기 전에 레지스트리에 항목을 구성하여 설치해야 합니다. 이 작업에는 대개 Regsvr32 유틸리티가 사용됩니다. COM+에서는 구성 요소를 COM+ 응용 프로그램으로 구성하면 이 과정이 자동으로 수행됩니다. COM 구성 요소는 Windows 2000에서 여전히 Regsvr32 유틸리티를 사용하여 등록할 수 있으며 COM+ 환경에서 구성되지 않은 구성 요소로 존재할 것입니다. 구성되지 않은 구성 요소는 구성 요소 서비스 관리 도구에 표시되지 않으며 새 COM+ 서비스를 사용하지 않습니다. 그럼에도 불구하고 이 구성 요소는 실행될 때 분산 COM+ 응용 프로그램 실행을 위해 제공되는 COM+ 인프라의 일부를 사용합니다.

COM+ 응용 프로그램은 하나 이상의 COM 구성 요소로 구성되어 있습니다. COM 클래스는 이름이 지정된 하나 이상의 구체적인 인터페이스입니다. 이 클래스는 메서드라고 하는 관련 함수 집합을 제공하는 클래스의 인터페이스를 표시합니다. COM 개체는 COM 클래스의 인스턴스입니다. COM 구성 요소는 COM 개체를 생성하는 이진 단위 코드이며 패키지 작성 및 등록 코드를 포함하고 있습니다.

compl02

COM 클래스는 CLSID(때로는 ProgID)에 의해 식별됩니다. 인터페이스는 규칙을 지정하는 관련 함수들의 그룹입니다. 여기에는 이름, 인터페이스 서명, 인터페이스 의미 및 마샬링 버퍼 형식이 포함됩니다.

인터페이스는 IID에 의해 식별됩니다. 인터페이스 구문은 IDL 및/또는 형식 라이브러리에서 정의됩니다. 클래스의 인터페이스는 관리할 수 있는 관련 메서드 집합으로 구분되어야 합니다. 인터페이스는 변경할 수 없다는 점에 유의하십시오. COM 규칙에는 인터페이스를 수정할 수 없다고 나와 있습니다. 메서드 추가와 같은 모든 수정은 새 인터페이스 정의가 필요합니다.

COM+ 응용 프로그램 구축

응용 프로그램 프로그래머는 COM+를 사용하여 구성 요소를 작성하고 이 구성 요소들을 응용 프로그램으로 통합하지만, 시스템 관리자의 업무는 대개 COM+ 응용 프로그램과 구성 요소를 설치, 구축, 구성하는 일입니다. 개발자는 부분적으로 구성된 COM+ 응용 프로그램을 시스템 관리자에게 전달합니다. 또는 외부에서 응용 프로그램이 제공되는 경우도 있습니다. 예를 들어, ISV(독립 소프트웨어 공급업체)로부터 COM+ 응용 프로그램을 구입할 수 있습니다. 그런 다음 관리자는 응용 프로그램을 특정 환경에 적합하게 적용합니다(예를 들어, 역할에 사용자 계정 추가 및 응용 프로그램 클러스터에 서버 이름 추가). 일반적인 관리 작업은 다음과 같습니다.

  • 관리 시스템에 부분적으로 구성된 COM+ 응용 프로그램 설치
  • 역할 구성원 및 개체 풀 크기 등의 특정 환경에 맞는 특성 제공
  • COM+ 응용 프로그램이 실행되는 ID(Windows 2000 사용자 계정) 설정
  • 완전한 구성의 COM+ 응용 프로그램 다시 내보내기
  • 응용 프로그램 프록시 생성(응용 프로그램을 원격으로 액세스하는 경우)

응용 프로그램이 특정 환경에 맞게 완전히 구성되면 관리자는 테스트 및/또는 제작 시스템에서 응용 프로그램을 구축할 수 있습니다. 여기에는 한 대 이상의 시스템에 완전한 구성의 COM+ 응용 프로그램을 설치하는 작업이 포함됩니다.

구성 요소 서비스 관리 도구는 도구에 포함된 응용 프로그램 내보내기 마법사를 사용하여 여러 서버에서 COM+ 응용 프로그램 구축을 편리하게 합니다. 구성 요소 서비스 관리 도구를 사용하면 COM+ 응용 프로그램 및 응용 프로그램 프록시의 설치 패키지를 생성할 수 있습니다. COM+는 Windows Installer 호환 설치 패키지를 생성합니다. 이 패키지에는 다른 시스템에 COM+ 응용 프로그램을 설치하는 데 필요한 모든 기능이 하나의 파일에 들어 있습니다.

compl03

COM+ 응용 프로그램을 포함하고 있는 .msi 파일은 COM+ 1.0 Services를 지원하는 컴퓨터에서만 설치할 수 있습니다(현재는 Windows 2000에서만 가능). 추가적인 장점으로 Windows Installer를 사용하여 설치한 COM+ 응용 프로그램은 Windows Installer 저작 도구를 사용하여 .msi 파일을 수정하지 않아도 프로그램 추가/제거 제어판에 나타납니다.

구성 요소 서비스 관리 도구에 의해 생성된 .msi 파일에는 다음과 같은 항목들이 포함됩니다.

  • COM+ 등록 정보를 가진 Windows Installer 테이블
  • 응용 프로그램의 특성을 포함하고 있는 .apl 파일
  • COM+ 응용 프로그램의 클래스에 의해 구현된 인터페이스를 나타내는 DLL 및 형식 라이브러리

.msi 파일과는 별도로 구성 요소 서비스 관리 도구는 캐비닛 파일(.cab)을 생성합니다. 이 파일은 Internet Explorer를 통해 COM+ 응용 프로그램을 구축할 수 있도록 .msi 파일을 효과적으로 포장합니다.

COM+ 응용 프로그램 프록시 설치

다른 (클라이언트) 컴퓨터에서 COM+ 서버 응용 프로그램을 원격으로 액세스하기 위해서는 클라이언트 컴퓨터가 DCOM 인터페이스 원격화에 필요한 프록시/스텁 DLL 및 형식 라이브러리를 포함하여 서버 응용 프로그램 특성의 하위 집합이 설치되어 있어야 합니다. 이 하위 집합을 응용 프로그램 프록시라고 합니다.

구성 요소 서비스 관리 도구를 통해 쉽게 COM+ 서버 응용 프로그램을 응용 프로그램 프록시로 내보낼 수 있습니다. COM+에 의해 생성되는 응용 프로그램 프록시는 표준 Windows Installer 설치 패키지입니다. 설치 후에 클라이언트 컴퓨터의 프로그램 추가/제거 제어판에 응용 프로그램 프록시가 나타납니다.

응용 프로그램 프록시를 생성할 때 COM+는 자동으로 다음과 같은 정보를 제공합니다. 이 정보는 응용 프로그램 프록시가 COM+ 서버 응용 프로그램을 원격으로 액세스하는 데 필요합니다.

  • 클래스 ID 정보(CLSID 및 ProgID). 응용 프로그램 프록시는 2개까지의 ProgID를 지원합니다.
  • 응용 프로그램 ID 및 응용 프로그램과 클래스의 관계(AppID)
  • 각 응용 프로그램의 위치 정보(원격 서버 이름)
  • 응용 프로그램에 의해 표시되는 모든 인터페이스의 마샬링 정보(예: 형식 라이브러리 및 프록시/스텁)
  • MSMQ 대기열 이름 및 ID(응용 프로그램에서 대기 중인 구성 요소 서비스를 사용할 수 있는 경우)
  • 역할 정보를 제외한 클래스, 인터페이스 및 특성
  • 응용 프로그램 특성

COM+ 서버 응용 프로그램과 달리 응용 프로그램 프록시는 DCOM 및 Windows Installer를 지원하는 모든 시스템에 설치할 수 있습니다. 다른 Windows 플랫폼의 클라이언트에서도 Windows 2000 서버에서 실행되는 COM+ 응용 프로그램을 액세스할 수 있습니다. Windows 2000을 실행하지 않아서 COM+가 부족한 컴퓨터에서는 DCOM 원격화에 필요한 정보의 하위 집합만 설치됩니다. 이 정보는 Windows 레지스트리에 설치됩니다. Windows 2000을 실행하지 않는 컴퓨터에 응용 프로그램 프록시(.msi 파일)를 설치하려면 해당 시스템에서 Windows Installer가 실행되고 있어야 합니다. Windows Installer는 Platform SDK의 일부처럼 재배포가 가능합니다.

COM+ 보안 설정

보안 역할은 COM+ 응용 프로그램의 액세스 제어 정책 모델을 만들고 실행합니다. 역할은 응용 프로그램의 리소스에 대한 액세스 허용 권한을 판별하기 위해 응용 프로그램에 대해 정의되는 사용자의 범주입니다. 개발자는 구성 요소, 인터페이스, 메서드 또는 개별 응용 프로그램 리소스를 포함한 역할을 상징적인 사용자 범주로서 응용 프로그램 및 이 응용 프로그램 내의 보다 세밀한 구조에 지정합니다. 이 역할 지정은 응용 프로그램에서 항목에 대한 액세스 권한을 가진 사용자 범주를 판별하는 데 사용됩니다.

응용 프로그램이 역할 기반 보안을 사용하면 응용 프로그램을 호출할 때마다 호출 프로그램의 역할 구성원을 검사합니다. 호출 프로그램이 호출된 항목에 대한 액세스 권한을 가진 역할에 속하지 않은 경우 호출이 실패합니다. 호출 프로그램은 역할에 정의된 제약 조건에 따라 응용 프로그램 및 리소스에 대한 액세스 권한을 엄격하게 승인합니다.

시스템 관리자의 업무는 Windows 2000 사용자 계정 및 그룹을 사용하는 응용 프로그램에 대해 정의된 역할을 채우는 것입니다. 이것은 응용 프로그램의 보안 정책을 수행하는 핵심 단계입니다. 사용자는 응용 프로그램을 통해 액세스할 데이터와 리소스에 대한 관계를 올바로 나타내는 역할을 지정해야 합니다.

사용자에게 역할을 채울 때 우선적으로 사용되는 방법은 Windows 2000 그룹을 사용하는 것입니다. 먼저 사용자 계정을 적절한 그룹에 지정한 다음 그룹이 적절한 역할에 지정되었는지 확인합니다. Windows 2000 그룹을 사용하여 역할을 채우면 많은 사용자를 관리하기가 쉬워집니다.

기업 컴퓨팅 환경에서는 대개 각 사용자의 위치를 효과적으로 추적하고 이것을 어떻게 각 응용 프로그램에 대해 특정한 역할 기반 보안 정책으로 매핑할지 결정하기가 어렵습니다. 사용자와 관리자 및 응용 프로그램의 수가 많아지면 이 작업은 점점 복잡해집니다. 가장 확장성 있는 해결책은 사용자 그룹을 COM+ 응용 프로그램 역할에 지정하는 것입니다.

역할에 그룹을 지정하기 전에 응용 프로그램의 보안 정책에 대해 확실히 이해할 필요가 있습니다. 이상적으로는 "관리자"나 "출납원"과 같이 역할에 포함할 사람을 제안하는 이름을 전달해야 합니다. 또한 어떤 종류의 사용자가 역할에 포함되어야 하는지 알 수 있도록 구성 요소 서비스 관리 도구를 사용하여 액세스할 수 있는 각 역할에 대한 설명이 있어야 합니다. 그러나 어떤 사용자 그룹이 어떤 역할에 속해야 하는지 확실히 모르는 경우에는 응용 프로그램과 함께 제공되는 설명서를 참조하거나 개발자에게 확인하십시오.

compl04

구성 요소 서비스 관리 도구를 사용하여 응용 프로그램을 설치하는 동안 역할에 초기 지정을 할 수 있고 응용 프로그램이 실행되는 동안 역할 구성원에 필요한 변경을 할 수도 있습니다.

개체 풀링

개체 풀링은 COM+에서 제공되는 자동 서비스이며, 구성 요소의 인스턴스가 풀에서 계속 활성 상태를 유지하면서 모든 클라이언트에서 필요한 구성 요소를 사용할 수 있도록 구성할 수 있습니다. 풀 크기 및 생성 요청 시간 제한 값과 같은 특성을 지정하여 주어진 구성 요소에 대해 풀이 유지되도록 구성하고 모니터할 수 있습니다. 일단 응용 프로그램이 실행되면 COM+는 풀을 관리하고 개체 동작의 세부 사항을 처리하고 지정한 기준에 따라 재사용합니다.

compl05

이러한 방법으로 개체를 재사용하면 특히 개체가 재사용의 장점을 충분히 활용하도록 작성된 경우 성능이 상당히 향상되고 확장성을 높일 수 있습니다. 개체 풀링을 통해 사용 가능한 하드웨어 리소스를 충분히 활용하도록 풀링을 구성할 수 있습니다. 사용 가능한 하드웨어 리소스가 변경되면 풀 구성이 변경될 수 있습니다. 또한 풀 관리를 통해 리소스 사용을 통제할 수 있습니다.

구성 요소가 풀링되도록 구성하면 COM+는 풀에서 구성 요소의 인스턴스를 유지하면서 클라이언트에서 구성 요소를 요구하는 경우에 활성화될 수 있습니다. 모든 개체 생성 요구는 풀 관리자를 통해 처리됩니다.

응용 프로그램이 시작되면 개체 생성에 성공한 동안 지정한 최소 수준까지 풀이 채워집니다. 구성 요소에 대한 클라이언트 요청이 시작되면 먼저 요청된 순서대로 풀로부터 처리됩니다. 풀링된 개체를 사용할 수 없고 아직 지정된 최대 수준으로 풀이 지정되지 않은 경우 클라이언트에 대해 새 개체가 생성되고 활성화됩니다.

풀이 최대 수준에 도달하면 클라이언트 요청이 대기열로 들어갑니다. 각 요청에 대해 풀에서 가장 먼저 사용할 수 있는 개체가 수신됩니다. 활성 개체와 비활성 개체의 수는 모두 최대 풀 값을 초과할 수 없습니다. 개체 생성 요청은 지정된 기간이 지난 후에 만료되므로 클라이언트가 개체 생성을 기다리는 시간을 조정할 수 있습니다. COM+는 풀이 최대 수준에 도달할 때까지 가능하면 클라이언트가 릴리스한 개체를 재사용하려고 시도합니다.

최대 풀 크기를 통해 사용할 리소스의 양을 세밀하게 제어할 수 있습니다. 예를 들어, 일정 수의 데이터베이스 연결에 대한 라이센스를 받은 경우 언제든지 몇 개의 연결을 열 지 제어할 수 있습니다.

클라이언트 사용 패턴, 개체 사용 특성 및 메모리와 연결과 같은 물리적 리소스를 고려하면 성능을 조율할 때 최적의 균형을 찾을 수 있습니다. 개체 풀링은 일정 시점이 지난 후에 반환이 줄어듭니다. 필요한 성능 수준과 이 성능을 얻기 위해 필요한 리소스 사이에서 균형점을 찾을 수 있습니다. 풀링을 통해 리소스 사용을 제어할 수 있습니다.

보다 간편한 COM+ 관리

이 내용이 새 구성 요소 서비스 관리 도구의 개요를 이해하는 데 도움이 될 수 있기를 바랍니다. 또한 도구 설계를 통해 원격 컴퓨터에서 COM+ 응용 프로그램을 더 쉽게 관리할 수 있는 방법을 올바로 이해할 수 있기를 바랍니다. 많은 질문과 의견 및 다음 기사에서 원하는 내용을 보내주시기 바랍니다.

COM+에 대해 더 자세히 알고 싶은 분들을 위한 풍부한 자료가 있습니다. 특히 아래의 서적들을 추천합니다. 이 서적들은 주로 개발자를 대상으로 하고 있지만 시스템 관리자에게도 많은 도움이 됩니다. 

posted by 방랑군 2009. 9. 8. 10:26

HOW TO: Set a Windows Hook in Visual C# .NET

적용 대상
This article was previously published under Q318804

SUMMARY

This article describes how to set a hook that is specific to a thread and to a hook procedure by using the mouse hook as an example. You can use hooks to monitor certain types of events. You can associate these events with a specific thread or with all of the threads in the same desktop as a calling thread.

back to the top

Set a Mouse Hook

To set a hook, call the SetWindowsHookEx function from the User32.dll file. This function installs an application-defined hook procedure in the hook chain that is associated with the hook.

To set a mouse hook and to monitor the mouse events, follow these steps:
  1. Start Microsoft Visual Studio .NET.
  2. On the File menu, point to New, and then click Project.
  3. In the New Project dialog box, click Visual C# Projects under Project Types, and then click Windows Application under Templates. In the Name box, type ThreadSpecificMouseHook. Form1 is added to the project by default.
  4. Add the following line of code in the Form1.cs file after the other using statements:
    using System.Runtime.InteropServices;
    					
  5. Add following code in the Form1 class:
    public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
    
    //Declare hook handle as int.
    static int hHook = 0;
    
    //Declare mouse hook constant.
    //For other hook types, you can obtain these values from Winuser.h in Microsoft SDK.
    public const int WH_MOUSE = 7;
    private System.Windows.Forms.Button button1;
    
    //Declare MouseHookProcedure as HookProc type.
    HookProc MouseHookProcedure;			
    
    //Declare wrapper managed POINT class.
    [StructLayout(LayoutKind.Sequential)]
    public class POINT 
    {
    	public int x;
    	public int y;
    }
    
    //Declare wrapper managed MouseHookStruct class.
    [StructLayout(LayoutKind.Sequential)]
    public class MouseHookStruct 
    {
    	public POINT pt;
    	public int hwnd;
    	public int wHitTestCode;
    	public int dwExtraInfo;
    }
    
    //Import for SetWindowsHookEx function.
    //Use this function to install thread-specific hook.
    [DllImport("user32.dll",CharSet=CharSet.Auto,
     CallingConvention=CallingConvention.StdCall)]
    public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, 
    IntPtr hInstance, int threadId);
    
    //Import for UnhookWindowsHookEx.
    //Call this function to uninstall the hook.
    [DllImport("user32.dll",CharSet=CharSet.Auto,
     CallingConvention=CallingConvention.StdCall)]
    public static extern bool UnhookWindowsHookEx(int idHook);
    		
    //Import for CallNextHookEx.
    //Use this function to pass the hook information to next hook procedure in chain.
    [DllImport("user32.dll",CharSet=CharSet.Auto,
     CallingConvention=CallingConvention.StdCall)]
    public static extern int CallNextHookEx(int idHook, int nCode, 
    IntPtr wParam, IntPtr lParam);  
    					
  6. Add a Button control to the form, and then add the following code in the Button1_click procedure:
    private void button1_Click(object sender, System.EventArgs e)
    {
    	if(hHook == 0)
    	{
    	        // Create an instance of HookProc.
    		MouseHookProcedure = new HookProc(Form1.MouseHookProc);
    				
    		hHook = SetWindowsHookEx(WH_MOUSE, 
    					MouseHookProcedure, 
    					(IntPtr)0,
    					AppDomain.GetCurrentThreadId());
    		//If SetWindowsHookEx fails.
    		if(hHook == 0 )
    		{
    			MessageBox.Show("SetWindowsHookEx Failed");
    			return;
    		}
    		button1.Text = "UnHook Windows Hook";
    	}
    	else
    	{
    		bool ret = UnhookWindowsHookEx(hHook);
    		//If UnhookWindowsHookEx fails.
    		if(ret == false )
    		{
    			MessageBox.Show("UnhookWindowsHookEx Failed");
    			return;
    		}
    		hHook = 0;
    		button1.Text = "Set Windows Hook";
    		this.Text = "Mouse Hook";
    	} 
    }
    					
  7. Add the following code for the MouseHookProc function in the Form1 class:
    public static int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
    	//Marshall the data from callback.
    	MouseHookStruct MyMouseHookStruct = (MouseHookStruct) Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
    
    	if (nCode < 0)
    	{
    		return CallNextHookEx(hHook, nCode, wParam, lParam);
    	}
    	else
    	{
    		//Create a string variable with shows current mouse. coordinates
    		String strCaption = "x = " + 
    				MyMouseHookStruct.pt.x.ToString("d") + 
    					"  y = " + 
    		MyMouseHookStruct.pt.y.ToString("d");
    		//Need to get the active form because it is a static function.
    		Form tempForm = Form.ActiveForm;
            
    		//Set the caption of the form.
    		tempForm.Text = strCaption;
    		return CallNextHookEx(hHook, nCode, wParam, lParam); 
    	}
    }
    					
  8. Press F5 to run the project, and then click the button on the form to set the hook. The mouse coordinates appear on the form caption bar when the pointer moves on the form. Click the button again to remove the hook.
back to the top

Global Hook Is Not Supported in .NET Framework

You cannot implement global hooks in Microsoft .NET Framework. To install a global hook, a hook must have a native dynamic-link library (DLL) export to inject itself in another process that requires a valid, consistent function to call into. This requires a DLL export, which .NET Framework does not support. Managed code has no concept of a consistent value for a function pointer because these function pointers are proxies that are built dynamically.

back to the top

REFERENCES

For more information about windows hooks, see the following MSDN documentation:

About Hooks
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/hooks_9rg3.asp

back to the top

posted by 방랑군 2009. 9. 8. 10:19

Juval Lowy

2003년 8월

요약: 이 기사에서는 generics가 다루는 문제, generics의 구현 방법, 프로그래밍 모델의 이점, 제약 조건, generic 메서드 및 위임, generic 상속 같은 고유하고 혁신적인 기능에 대해 논의합니다(41페이지/인쇄 페이지 기준).

GenericsInCSharp.msi 예제 파일을 다운로드하십시오.

참고    이 기사를 이해하려면 C# 1.1에 익숙해야 합니다. C# 언어에 대한 자세한 내용을 보려면 http://msdn.microsoft.com/vcsharp/language 를 방문하십시오.

목차

소개
generics 문제 정의
generics의 정의
generics 적용
generic 제약 조건
generics 및 캐스팅
상속 및 generics
generic 메서드
generic 위임
generics 및 반사
generics 및 .NET Framework
결론

소개

generics는 C# 2.0에서 가장 강력하고 기대되는 기능입니다. generics를 사용하면 실제 데이터 형식을 커밋하지 않고도 형식이 안전한 데이터 구조를 정의할 수 있습니다. 그러면 형식별 코드를 복제하지 않고도 데이터 처리 알고리즘을 다시 사용할 수 있기 때문에 성능이 크게 향상되고 코드의 품질이 높아집니다. 개념적으로 generics는 C++ 템플릿과 비슷하지만 구현 및 성능 면에서는 크게 다릅니다. 이 기사에서는 generics가 다루는 문제, generics의 구현 방법, 프로그래밍 모델의 이점, 제약 조건, generic 메서드 및 위임, generic 상속 같은 고유하고 혁신적인 기능에 대해 논의합니다. 반사, 컬렉션, serialization, 원격 등 .NET Framework의 다른 영역에서 generics를 활용하는 방법과 기본적으로 제공되는 기능을 향상시키는 방법에 대해서도 설명합니다.

generics 문제 정의

예를 들어 일반적인 Push()Pop() 메서드를 제공하는 스택 같은 일상적인 데이터 구조가 있는 경우, 범용 스택을 개발할 때는 이를 사용하여 다양한 형식의 인스턴스를 저장할 수 있습니다. C# 1.1에서는 개체 기반 스택을 사용해야 합니다. 즉, 스택에 사용되는 내부 데이터 형식은 무정형 개체이며 스택 메서드는 개체와 상호 작용합니다.

public class Stack
{
   object[] m_Items; 
   public void Push(object item)
   {...}
   public object Pop() 
   {...}
} (참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

코드 블록 1은 개체 기반 스택의 전체 구현을 보여 줍니다. 개체는 정식 .NET 기반 형식이므로 개체 기반 스택을 사용하여 정수 등 모든 형식의 항목을 보관할 수 있습니다.

Stack stack = new Stack();
stack.Push(1);
stack.Push(2);
int number = (int)stack.Pop();

코드 블록 1: 개체 기반 스택

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("Cannot pop an empty stack");
      }
   }
}

그러나 개체 기반 솔루션에는 두 가지 문제가 있습니다. 첫 번째 문제는 성능입니다. 값 형식을 사용할 경우 푸시하고 저장하려면 box하고, 스택에서 꺼내려면 unbox해야 합니다. boxing 및 unboxing은 그 자체만으로도 성능을 크게 저하시키지만 관리 힙의 부담도 늘어나므로 성능에 좋지 않은 영향을 미치는 가비지 수집이 증가하게 됩니다. 값 형식 대신 참조 형식을 사용하는 경우에도 개체에서 상호 작용하고 있는 실제 형식으로 캐스팅해야 하고 캐스팅에 따른 추가 작업이 필요하므로 성능이 여전히 저하됩니다.

Stack stack = new Stack();
stack.Push("1");
string number = (string)stack.Pop();

개체 기반 솔루션의 두 번째이자 더 심각한 문제는 형식의 안전성입니다. 컴파일러에서는 모든 내용을 개체로 캐스팅하거나 개체에서 캐스팅할 수 있으므로 컴파일 시 형식의 안전성이 낮아집니다. 예를 들어 다음 코드는 제대로 컴파일되지만 런타임에 잘못된 캐스트 예외가 발생합니다.

Stack stack = new Stack();
stack.Push(1);
//컴파일되지만 형식이 안전하지 않으며 예외를 throw합니다. 
string number = (string)stack.Pop();

형식별, 즉 형식이 안전한 퍼포먼트 스택을 사용하여 이 두 가지 문제를 해결할 수 있습니다. 정수의 경우 IntStack을 구현하고 사용할 수 있습니다.

public class IntStack
{
   int[] m_Items; 
   public void Push(int item){...}
   public int Pop(){...}
} 
IntStack stack = new IntStack();
stack.Push(1);
int number = stack.Pop();

문자열의 경우 StringStack을 구현합니다.

public class StringStack
{
   string[] m_Items; 
   public void Push(string item){...}
   public string Pop(){...}
}
StringStack stack = new StringStack();
stack.Push("1");
string number = stack.Pop();

나머지도 비슷합니다. 안타깝게도 이 방법으로 성능 및 형식 안전성 문제를 해결할 경우 이에 못지 않은 세 번째 문제, 즉 작업 생산성이 낮아지는 상황이 발생합니다. 형식별 데이터 구조를 작성하는 작업은 더디고 반복적이며 오류가 발생하기 쉽습니다. 데이터 구조에서 결함을 수정할 경우 한 곳에서만 해결하면 되는 것이 아니라 결과적으로는 동일한 데이터 구조인 형식별 중복 항목이 있는 곳마다 결함을 해결해야 합니다. 또한 알 수 없거나 아직 정의되지 않은 미래의 형식이 사용될지도 모르므로 개체 기반 데이터 구조도 유지해야 합니다. 따라서 대부분의 개발자는 형식별 데이터 구조가 실용적이지 못하다고 판단하여 결함이 있음에도 불구하고 개체 기반 데이터 구조를 사용합니다.

generics의 정의

generics를 사용하면 형식 안전성, 성능 또는 생산성을 그대로 유지하면서 형식이 안전한 클래스를 정의할 수 있습니다. 서버는 단 한 번만 generic 서버로 구현하게 되지만, 동시에 서버를 선언하여 어떤 형식에나 사용할 수 있습니다. 이를 위해 <> 대괄호로 generic 형식 매개 변수를 둘러싸십시오. 예를 들어 다음과 같이 generic 스택을 정의하고 사용합니다.

public class Stack<T>
{
   T[] m_Items; 
   public void Push(T item)
   {...}
   public T Pop()
   {...}
}
Stack<int> stack = new Stack<int>();
stack.Push(1);
stack.Push(2);
int number = stack.Pop();

코드 블록 2는 generic 스택의 전체 구현을 보여 줍니다. 코드 블록 1코드 블록 2를 비교하여 코드 블록 1에서 사용된 모든 object코드 블록 2에서 T로 대체되었는지 확인하십시오. generic 형식 매개 변수 T를 사용하여 Stack을 정의하는 것은 예외입니다.

public class Stack<T>
{...}

generic 스택을 사용할 경우 변수를 선언할 때와 인스턴스화할 때 모두 generic 형식 매개 변수 T 대신 사용할 형식을 컴파일러에 지정해야 합니다.

Stack<int> stack = new Stack<int>();

나머지는 컴파일러와 런타임에서 자동으로 수행합니다. T를 받아들이거나 반환하는 모든 메서드(속성)는 지정된 형식을 대신 사용하게 됩니다. 위 예제의 경우에는 정수를 사용합니다.

코드 블록 2. generic 스택

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("Cannot pop an empty stack");
      }
   }
}
참고    T는 generic 형식이 Stack<T>일 경우의 generic 형식 매개 변수(형식 매개 변수)입니다.

이 프로그래밍 모델의 이점은 클라이언트가 서버 코드를 사용하는 방법에 따라 실제 데이터 형식은 변경될 수 있지만 내부 알고리즘과 데이터 조작은 그대로 유지된다는 것입니다.

generics 구현

표면적으로 C# generics는 C++ 템플릿과 매우 유사하지만 컴파일러에서 구현되고 지원되는 방법에는 중요한 차이점이 있습니다. 이 기사의 뒷부분에 나와 있듯이 둘 사이의 차이점은 generics를 사용하는 방법에 큰 영향을 미칩니다. C++ 템플릿과 비교하여 C# generics는 좀 더 안전하지만 기능이 다소 제한되어 있습니다.

C++에서 템플릿은 사실 매크로일 뿐이며 컴파일된 이진수로 유지되지 않습니다. 특정 형식의 템플릿 클래스를 사용하지 않으면 컴파일러가 템플릿 코드를 컴파일할 수도 없습니다. 형식을 지정하면 컴파일러가 코드를 인라인에 삽입하여 generic 형식 매개 변수의 모든 항목을 지정된 형식으로 바꿉니다. 템플릿 클래스에서 발생한 컴파일 오류는 템플릿 클래스를 사용할 때만 발견할 수 있습니다. 또한 특정 형식을 사용할 때마다 컴파일러는 사용자가 이미 응용 프로그램의 다른 곳에서 템플릿 클래스에 대해 해당 형식을 지정했는지에 관계없이 형식별 코드를 삽입합니다. 따라서 코드가 비대해져 로드 시간이 길어질 뿐 아니라 메모리 공간도 많이 차지하게 됩니다.

.NET 2.0에서는 generics가 IL(Intermediate Language) 및 CLR 자체를 기본적으로 지원합니다. generic C# 서버 쪽 코드를 컴파일하면 컴파일러가 이를 다른 모든 형식과 마찬가지로 IL로 컴파일합니다. 그러나 IL에는 실제 특정 형식의 매개 변수나 자리 표시자만 들어 있습니다. 또한 generic 서버의 메타데이터에는 generic 정보가 들어 있습니다.

클라이언트 쪽 컴파일러는 해당 generic 메타데이터를 사용하여 형식의 안전성을 지원합니다. 클라이언트가 generic 형식 매개 변수 대신 특정 형식을 제공하는 경우 클라이언트의 컴파일러는 서버 메타데이터에 있는 generic 형식 매개 변수를 지정된 형식으로 대체합니다. 그러면 generics가 전혀 사용되지 않은 것처럼 클라이언트의 컴파일러에 서버의 형식별 정의가 제공됩니다. 이러한 방법으로 클라이언트 컴파일러는 올바른 메서드 매개 변수, 형식 안전성 검사 및 형식별 IntelliSense®도 적용할 수 있습니다.

흥미로운 점은 .NET이 서버의 generic IL을 기계어 코드로 컴파일하는 방식입니다. 사실, 실제로 만들어지는 기계어 코드는 지정된 형식이 값 형식인지 아니면 참조 형식인지에 따라 달라집니다. 클라이언트가 값 형식을 지정하면 JIT 컴파일러가 IL에 있는 generic 형식 매개 변수를 특정 값 형식으로 바꾸고 네이티브 코드로 컴파일합니다. 그러나 JIT 컴파일러는 이미 생성한 형식별 서버 코드를 추적합니다. 이미 기계어 코드로 컴파일한 값 형식을 사용하여 generic 서버를 컴파일하도록 JIT 컴파일러에 지정하는 경우 해당 서버 코드에 대한 참조만 반환됩니다. JIT 컴파일러는 차후의 모든 항목에서 동일한 값 형식별 서버 코드를 사용하므로 코드가 비대해지지 않습니다.

클라이언트가 참조 형식을 지정하면 JIT 컴파일러가 서버 IL에 있는 generic 매개 변수를 개체로 바꾸고 네이티브 코드로 컴파일합니다. 해당 코드는 차후의 모든 참조 형식 요청에서 generic 형식 매개 변수 대신 사용됩니다. 이러한 방법으로 JIT 컴파일러는 실제 코드만 다시 사용합니다. 인스턴스는 여전히 크기에 따라 관리 힙에 할당되며 캐스팅은 없습니다.

generics 이점

.NET에서는 generics를 구현할 때 사용한 코드와 작업을 generics를 사용할 때 다시 사용할 수 있습니다. 값 형식을 사용하거나 참조 형식을 사용하거나에 관계없이 코드를 비대하게 만들지 않고도 형식 및 내부 데이터를 변경할 수 있습니다. 코드를 한 번 개발하고 테스트하고 배포한 후에는 모든 컴파일러 지원과 형식 안전성이 보장되는 상태에서 미래의 형식을 포함한 모든 형식에 다시 사용할 수 있습니다. generic 코드를 사용하면 값 형식을 boxing 및 unboxing하거나 참조 형식을 다운 캐스팅하지 않아도 되므로 성능이 크게 향상됩니다. 값 형식을 사용하면 형식에 액세스할 때 일반적으로 성능이 200% 향상되며, 참조 형식을 사용하면 100%의 성능 향상을 기대할 수 있습니다. 물론 전체 응용 프로그램의 성능은 향상될 수도 있고 그렇지 않을 수도 있습니다. 이 기사에서 사용한 소스 코드에는 간단한 루프에서 스택을 실행하는 마이크로 벤치마크 응용 프로그램이 포함되어 있습니다. 이 응용 프로그램을 사용하면 개체 기반 스택과 generic 스택에서 값 형식 및 참조 형식을 사용해 볼 수 있으며 루프 반복의 수를 변경하여 generics가 성능에 미치는 영향을 확인할 수도 있습니다.

generics 적용

IL 및 CLR에서 generics를 기본적으로 지원하므로 대부분의 CLR 호환 언어는 generic 형식을 활용할 수 있습니다. 예를 들어 다음은 코드 블록 2의 generic 스택을 사용하는 몇 가지 Visual Basic® .NET 코드입니다.

Dim stack As Stack(Of Integer)
stack = new Stack(Of Integer)
stack.Push(3)
Dim number As Integer
number = stack.Pop()

클래스 및 구조체에서 generics를 사용할 수 있습니다. 다음은 유용한 generic point 구조체입니다.

public struct Point<T>
{
   public T X;
   public T Y;
}

예를 들어 generic point를 정수 좌표에 사용할 수 있습니다.

Point<int> point;
point.X = 1;
point.Y = 2;

또는 부동 소수점 정밀도가 필요한 차트 좌표에 사용할 수 있습니다.

Point<double> point;
point.X = 1.2;
point.Y = 3.4;

지금까지 소개한 기본적인 generics 구문 이외에도 C# 2.0은 generics를 사용할 때 일부 C# 1.1 구문을 오버로드하여 특수화합니다. 코드 블록 2Pop() 메서드를 예로 들어 보겠습니다. 스택이 비어 있을 때 예외를 throw하는 대신 스택에 저장된 형식의 기본값을 반환할 수 있습니다. 개체 기반 스택을 사용하는 경우 null만 반환하면 되지만 generic 스택이 값 형식과 함께 사용될 수도 있습니다. 이 문제를 해결하기 위해 모든 generic 형식 매개 변수는 형식의 기본값을 반환하는 default라는 속성을 지원합니다.

다음은 Pop() 메서드 구현에서 기본값을 사용하는 방법입니다.

public T Pop()
{
   m_StackPointer--;
   if(m_StackPointer >= 0)
   {
      return m_Items[m_StackPointer];
   }
   else
   {
      m_StackPointer = 0;
      return T.default;
   }
}

참조 형식의 기본값은 null이고 정수, 열거 및 구조 같은 값 형식의 기본값은 구조를 0으로 채우는 제로 화이트워시입니다. 따라서 스택이 문자열로 구성되어 있으면 스택이 비어 있을 때 Pop() 메서드가 null을 반환하고, 스택이 정수로 구성되어 있으면 스택이 비어 있을 때 Pop() 메서드가 0을 반환합니다.

복수 generic 형식

단일 형식은 복수 generic 형식 매개 변수를 정의할 수 있습니다. 코드 블록 3에 나와 있는 generic 연결 리스트를 예로 들어 보겠습니다.

코드 블록 3. generic 연결 리스트

class Node<K,T>
{
   public Node()
   {
      Key      = K.default;
      Item     = T.default;
      licenseStream = null;
   }
   public Node(K key,T item,Node<K,T> nextNode)
   {
      Key      = key;
      Item     = item;
      NextNode = nextNode;
   }
   public K Key;
   public T Item;
   public Node<K,T> NextNode;
}

public class LinkedList<K,T>
{
   public LinkedList()
   {
      m_Head = new Node<K,T>();
   }
   public void AddHead(K key,T item)
   {
      Node<K,T> newNode = new Node<K,T>(key,item,m_Head.NextNode);
      m_Head.NextNode = newNode;
   }
   Node<K,T> m_Head;
}

연결 리스트에는 노드가 저장됩니다.

class Node<K,T>
{...}

각 노드에는 generic 형식 매개 변수 K의 키와 generic 형식 매개 변수 T의 값이 들어 있습니다. 리스트에 있는 다음 노드에 대한 참조도 들어 있습니다. 연결 리스트 자체는 generic 형식 매개 변수 K 및 T에 의해 정의됩니다.

public class LinkedList<K,T>
{...}

이로 인해 리스트는 AddHead() 같은 generic 메서드를 노출할 수 있습니다.

public void AddHead(K key,T item);

generics를 사용하는 형식의 변수를 선언할 때마다 사용할 형식을 지정해야 합니다. 그러나 지정된 형식 자체가 generic 형식일 수도 있습니다. 예를 들어 연결 리스트에 노드 형식의 m_Head라는 멤버 변수가 있으며 리스트에 있는 첫 번째 항목을 참조하는 데 사용됩니다. m_Head는 리스트의 자체 generic 형식 매개 변수 K 및 T를 사용하여 선언됩니다.

Node<K,T> m_Head;

노드를 인스턴스화할 때 특정 형식을 제공해야 하며 다시 한 번 연결 리스트의 자체 generic 형식 매개 변수를 사용할 수 있습니다.

public void AddHead(K key,T item)
{
   Node<K,T> newNode = new Node<K,T>(key,item,m_Head.NextNode);
   m_Head.NextNode = newNode;
}

이 리스트에서 generic 형식 매개 변수의 노드와 동일한 이름을 사용한 것은 단지 이해를 돕기 위함입니다. 다음과 같이 다른 이름을 사용할 수도 있습니다.

public class LinkedList<U,V>
{...}

또는

public class LinkedList<KeyType,DataType>
{...}

이 경우 m_Head는 다음과 같이 선언됩니다.

Node<KeyType,DataType> m_Head;

연결 리스트를 사용 중인 클라이언트는 특정 형식을 제공해야 합니다. 클라이언트는 정수를 키로, 문자열을 데이터 항목으로 선택할 수 있습니다.

LinkedList<int,string> list = new LinkedList<int,string>();
list.AddHead(123,"AAA");

그러나 클라이언트가 키의 타임스탬프 같은 다른 조합을 선택할 수도 있습니다.

LinkedList<DateTime,string> list = new LinkedList<DateTime,string>();
list.AddHead(DateTime.Now,"AAA");   

때로는 특정 형식의 특정 조합에 별칭을 지정하는 것이 좋습니다. 코드 블록 4에 나와 있는 것처럼 using 문을 사용하면 됩니다. 별칭 지정의 범위는 파일의 범위이므로 using 네임스페이스를 사용할 때와 같은 방법으로 프로젝트 파일 전체에서 별칭 지정을 반복해야 합니다.

코드 블록 4. generic 형식 별칭 지정

using List = LinkedList<int,string>;

class ListClient
{
   static void Main(string[] args)
   {
      List list = new List();
      list.AddHead(123,"AAA");
   }
}

generic 제약 조건

특정 형식이 제공되어야 컴파일러가 템플릿 코드를 컴파일하는 C++에서와 달리 C# generics에서는 클라이언트가 사용할 특정 형식에 관계없이 컴파일러가 generic 코드를 IL로 컴파일합니다. 따라서 generic 코드는 클라이언트가 사용하는 특정 형식과 호환되지 않는 generic 형식 매개 변수의 메서드, 속성 또는 멤버를 사용하려고 할 수 있습니다. 이는 결과적으로 형식 안전성을 떨어뜨리기 때문에 허용되지 않습니다. C#에서는 클라이언트가 지정한 형식이 준수해야 하는 제약 조건을 컴파일러에 지정하여 generic 형식 매개 변수 대신 사용되도록 해야 합니다. 두 가지 유형의 제약 조건이 있습니다. 파생 제약 조건은 generic 형식 매개 변수가 인터페이스 또는 특정 기본 클래스 같은 기본 형식에서 파생된다는 것을 컴파일러에 알립니다. 기본 생성자 제약 조건은 generic 형식 매개 변수가 기본 public 생성자(매개 변수가 없는 public 생성자)를 노출한다는 것을 컴파일러에 알립니다. generic 형식에는 여러 제약 조건이 있을 수 있으며 기본 형식으로부터 메서드 또는 멤버를 제안하는 것과 같이 generic 형식 매개 변수를 사용할 때 제약 조건을 반영하는 IntelliSense도 있습니다.

제약 조건은 선택적이지만 generic 형식을 개발할 때 필수적인 경우가 종종 있습니다. 제약 조건이 없으면 컴파일러가 좀 더 신중하고 형식이 안전한 접근 방식을 사용하며 generic 형식 매개 변수에서 개체 수준 기능만 사용할 수 있도록 합니다. 제약 조건은 generic 형식 메타데이터의 일부분이므로 클라이언트 쪽 컴파일러도 제약 조건을 활용할 수 있습니다. 클라이언트 쪽 컴파일러를 사용하는 클라이언트 개발자는 제약 조건을 준수하는 형식만을 사용할 수 있으므로 형식 안전성이 보장됩니다.

긴 예를 들어 제약 조건의 필요성과 사용 방법을 설명해 보겠습니다. 코드 블록 3의 연결 리스트에 인덱싱 기능 또는 키 기준 검색 기능을 추가하려고 합니다.

public class LinkedList<K,T>
{
   T Find(K key)
   {...}
   public T this[K key]
   {
      get{return Find(key);}
   }
}

그러면 클라이언트가 다음 코드를 작성할 수 있습니다.

LinkedList<int,string> list = new LinkedList<int,string>();

list.AddHead(123,"AAA");
list.AddHead(456,"BBB");
string item = list[456];
Debug.Assert(item == "BBB");

검색 기능을 구현하려면 리스트를 검사하고, 각 노드의 키를 검색 중인 키와 비교하고, 키가 일치하는 노드의 항목을 반환해야 합니다. 문제는 다음 Find() 구현이 컴파일되지 않는다는 것입니다.

T Find(K key)
{
   Node<K,T> current = m_Head;
   while(current.NextNode != null)
   {
      if(current.Key == key) //컴파일되지 않습니다.
         break; 
      else
         current = current.NextNode;
   }
   return current.Item; 
}

그 이유는 컴파일러가 다음 줄의 컴파일을 거부하기 때문입니다.

if(current.Key == key)

컴파일러는 K(클라이언트가 제공하는 실제 형식)가 == 연산자를 지원하는지 알지 못하므로 위 줄은 컴파일되지 않습니다. 예를 들어 구조체는 기본적으로 이러한 구현을 제공하지 않습니다. IComparable 인터페이스를 사용하여 == 연산자 문제를 해결할 수 있습니다.

public interface IComparable 
{
   int CompareTo(object obj);
}

CompareTo()는 비교한 개체가 인터페이스를 구현 중인 개체와 일치할 경우 0을 반환하므로 Find() 메서드에서 다음과 같이 사용할 수 있습니다.

if(current.Key.CompareTo(key) == 0)

안타깝게도 K(클라이언트가 제공하는 실제 형식)가 IComparable에서 파생되었다는 것을 컴파일러가 알 수 있는 방법이 없으므로 이 줄도 컴파일되지 않습니다.

IComparable에 명시적으로 캐스팅하여 컴파일러가 비교 줄을 컴파일하도록 할 수도 있지만 그럴 경우에는 형식 안전성이 낮아지는 것을 감수해야 합니다.

if(((IComparable)(current.Key)).CompareTo(key) == 0)

클라이언트가 사용하는 형식이 IComparable에서 파생되지 않으면 런타임 예외가 발생합니다. 또한 사용된 키 형식이 키 형식 매개 변수가 아니라 값 형식이면 키의 boxing을 강요하는 것이므로 성능에 다소 영향을 미칠 수 있습니다.

파생 제약 조건

C# 2.0에서는 예약된 키워드 where를 사용하여 제약 조건을 정의합니다. generic 형식 매개 변수에서 뒤에 파생 콜론이 붙은 where 키워드를 사용하여 generic 형식 매개 변수가 특정 인터페이스를 구현한다는 것을 컴파일러에 알립니다. 예를 들어 다음은 LinkedList의 Find() 메서드를 구현하는 데 필요한 파생 제약 조건입니다.

public class LinkedList<K,T> where K : IComparable
{
   T Find(K key)
   {
      Node<K,T> current = m_Head;
      while(current.NextNode != null)
      {
         if(current.Key.CompareTo(key) == 0)
            break; 
         else
            current = current.NextNode;
      }
      return current.Item; 
   }
   //구현의 나머지 부분
}

제약 조건에서 IComparable을 사용할 수는 있지만 사용된 키가 정수 같은 값 형식일 경우에는 boxing으로 인해 여전히 성능이 저하됩니다. 이를 해결하기 위해 System.Collections.Generics 네임스페이스는 generic 인터페이스 IComparable<T>를 정의합니다.

public interface IComparable<T> 
{
   int CompareTo(T other);
}

키의 형식을 형식 매개 변수로 사용하여 키 형식 매개 변수가 IComparable<T>를 지원하도록 제한할 수 있습니다. 이 경우 형식 안전성이 보장될 뿐만 아니라 키로 사용될 때 값 형식을 boxing할 필요도 없습니다.

public class LinkedList<K,T> where K : IComparable<K>
{...}

사실 .NET 1.1에서 IComparable을 지원하던 형식은 모두 .NET 2.0에서 IComparable<T>를 지원합니다. 따라서 int, string, Guid, DateTime 등 일반 형식의 키를 사용할 수 있습니다.

C# 2.0에서는 모든 제약 조건이 generic 클래스의 실제 파생 목록 뒤에 와야 합니다. 예를 들어 LinkedListIEnumerable<T> 인터페이스(iterator 지원)에서 파생된 경우 where 키워드를 바로 뒤에 넣습니다.

public class LinkedList<K,T> : IEnumerable<T> where K : IComparable<K>
{...}

제약 조건은 LinkedList 클래스의 선언 줄에서 generic 형식 매개 변수 K가 정의되는 위치에 나타납니다. 이로 인해 LinkedList 클래스는 K를 형식 매개 변수로 사용하여 IComparable<T>를 구현하는 것처럼 generic 형식 매개 변수 K를 처리할 수 있습니다. 제한하는 인터페이스의 메서드에 대한 IntelliSense 지원도 받습니다.

리스트의 키에 대한 구체적인 형식을 제공하는 LinkedList 형식의 변수를 클라이언트가 선언할 때 클라이언트 쪽 컴파일러는 키 형식이 IComparable<T>(키의 형식을 형식 매개 변수로 사용)에서 파생되도록 요구합니다. 그렇지 않은 경우에는 클라이언트 코드 작성을 거부합니다.

일반적으로는 필요한 수준에서만 제약 조건을 정의해야 합니다. 연결 리스트 예제에서 노드 자체는 키를 비교하지 않기 때문에 노드 수준에서 IComparable<T> 파생 제약 조건을 정의하는 것은 아무런 의미가 없습니다. 노드 수준에서 파생 제약 조건을 정의한 경우 리스트가 키를 비교하지 않는다고 해도 LinkedList 수준에서도 제약 조건을 넣어야 합니다. 리스트에 노드가 멤버 변수로 포함되어 있어서 컴파일러는 노드가 generic 키 형식에 넣은 제약 조건을 리스트 수준에서 정의된 키 형식이 준수하도록 요구하기 때문입니다.

다시 말해, 노드를 다음과 같이 정의합니다.

class Node<K,T> where K : IComparable<K>
{...}

이 경우 리스트 수준에 대해 Find() 메서드나 기타 메서드를 제공하지 않더라도 리스트 수준에서 제약 조건을 반복해야 합니다.

public class LinkedList<KeyType,DataType> where KeyType : IComparable<KeyType>
{
   Node<KeyType,DataType> m_Head;
}

같은 generic 형식 매개 변수에 있는 여러 인터페이스를 쉼표로 구분하여 제한할 수 있습니다. 다음 예를 살펴봅시다.

public class LinkedList<K,T> where K : IComparable<K>,IConvertible
{...}

예를 들어 클래스가 사용하는 모든 generic 형식 매개 변수에 대한 제약 조건을 제공할 수 있습니다.

public class LinkedList<K,T> where K : IComparable<K>
                             where T : ICloneable 
{...}

generic 형식 매개 변수가 특정 기본 클래스에서 파생되는 것을 의미하며 이를 규정하는 기본 클래스 제약 조건을 사용할 수 있습니다.

public class MyBaseClass
{...}
public class LinkedList<K,T> where K : MyBaseClass
{...}

그러나 C#는 구현의 복수 상속을 지원하지 않기 때문에 제약 조건에서는 기본 클래스를 하나만 사용할 수 있습니다. 당연히 제한한 기본 클래스는 봉인(sealed) 클래스가 될 수 없으므로 컴파일러가 기본 클래스를 적용합니다. 또한 System.Delegate 또는 System.Array를 기본 클래스로 제한할 수 없습니다.

기본 클래스와 하나 이상의 인터페이스를 모두 제한할 수 있지만 파생 제약 조건 목록에서 기본 클래스가 먼저 나와야 합니다.

public class LinkedList<K,T> where K : MyBaseClass, IComparable<K>
{...}

C#에서는 naked generic 형식 매개 변수를 제약 조건으로 지정할 수 없습니다.

public class LinkedList<K,T,U> where K : U //컴파일되지 않습니다.
{...}

그러나 C#에서 다른 generic 형식을 제약 조건으로 사용할 수는 있습니다.

public interface ISomeInterface<T>
{...}
public class LinkedList<K,T> where K : ISomeInterface<int>
{...}

다른 generic 형식을 기본 형식으로 제한하는 경우 사용자 고유의 형식 매개 변수에 대한 generic 형식 매개 변수를 지정하여 해당 형식 generic을 유지할 수 있습니다. 예를 들어 generic 인터페이스 제약 조건의 경우는 다음과 같습니다.

public class LinkedList<K,T> where K : ISomeInterface<T>
{...}

generic 기본 클래스 제약 조건의 경우는 다음과 같습니다.

public class MySubClass<T> where T : MyBaseClass<T>
{...}

마지막으로 파생 제약 조건을 제공하는 경우 제한하는 기본 형식(인터페이스 또는 기본 클래스)은 정의하는 generic 형식 매개 변수와 일관되게 표시되어야 합니다. 예를 들어 다음 제약 조건은 internal 형식이 public 형식을 사용할 수 있기 때문에 유효합니다.

public class MyBaseClass
{}
internal class MySubClass<T> where T : MyBaseClass
{}

그러나 다음과 같이 두 클래스의 표시 순서가 바뀐 경우는 다릅니다.

internal class MyBaseClass
{}
public class MySubClass<T> where T : MyBaseClass
{}

이 경우 public 형식 대신 internal 형식으로 적용된 MySubClass를 렌더링하면서 generic 형식 MySubClass를 사용할 수 있는 어셈블리 외부의 클라이언트가 없기 때문에 컴파일러에서 오류가 발생합니다. 외부 클라이언트가 MySubClass를 사용할 수 없는 이유는 MySubClass 형식의 변수를 선언하려면 internal 형식 MyBaseClass에서 파생되는 형식을 사용해야 하기 때문입니다.

생성자 제약 조건

generic 클래스 내에서 새 generic 개체를 인스턴스화하려고 합니다. 이 경우 문제는 클라이언트가 사용할 특정 형식에 일치하는 생성자가 있는지 C# 컴파일러가 알지 못하므로 인스턴스화 줄의 컴파일을 거부한다는 것입니다.

이 문제를 해결하기 위해 C#에서는 generic 형식 매개 변수가 public 기본 생성자를 반드시 지원하도록 제한할 수 있습니다. new() 제약 조건을 사용하면 됩니다. 예를 들어 다음은 코드 블록 3에서 generic Node <K,T>의 기본 생성자를 구현하는 여러 가지 방법입니다.

class Node<K,T> where T : new() 
{
   public Node()
   {
      Key      = K.default;
      Item     = new T();
      licenseStream = null;
   }
   public K Key;
   public T Item;
   public Node<K,T> NextNode;
}

생성자 제약 조건을 파생 제약 조건과 결합할 수 있습니다. 단, 제약 조건 목록에서 생성자 제약 조건이 마지막에 나와야 합니다.

public class LinkedList<K,T> where K : IComparable<K>,new() 
{...}

generics 및 캐스팅

코드 블록 5에 나와 있듯이 C# 컴파일러에서는 generic 형식 매개 변수를 개체 또는 제약 조건이 지정된 형식에만 암시적으로 캐스팅할 수 있습니다. 이와 같이 암시적으로 캐스팅하면 컴파일할 때 비호환성을 모두 찾을 수 있으므로 형식상 안전합니다.

코드 블록 5. generic 형식 매개 변수의 암시적 캐스팅

interface ISomeInterface
{...}
class BaseClass
{...}
class MyClass<T> where T : BaseClass,ISomeInterface
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = t;
      BaseClass      obj2 = t;
      object         obj3 = t;
   }
}

컴파일러에서 generic 형식 매개 변수를 다른 모든 인터페이스에 명시적으로 캐스팅할 수 있지만 클래스에는 명시적으로 캐스팅할 수 없습니다.

interface ISomeInterface
{...}
class SomeClass
{...}
class MyClass<T> 
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = (ISomeInterface)t;//컴파일됩니다.
      SomeClass      obj2 = (SomeClass)t;     //컴파일되지 않습니다.
   }
}

그러나 임시 Object 변수를 사용하여 강제로 generic 형식 매개 변수에서 다른 형식으로 캐스팅되도록 할 수 있습니다.

class SomeClass
{...}

class MyClass<T> 
{
   void SomeMethod(T t)
   {
      object temp = t;
      SomeClass obj = (SomeClass)temp;
   }
}

물론, generic 형식 매개 변수 대신 사용된 구체적인 형식이 명시적으로 캐스팅하는 형식에서 파생되지 않는 경우에는 런타임에 예외가 throw될 수 있으므로 이러한 명시적 캐스팅에는 위험이 따릅니다. 예외가 발생할 위험이 있는 캐스팅을 수행하는 대신 코드 블록 6에서와 같이 isas 연산자를 사용하는 것이 더 나은 방법입니다. is 연산자는 generic 형식 매개 변수가 쿼리한 형식의 것일 경우 true를 반환하며, as는 형식이 호환되면 캐스트를 수행하고 호환되지 않으면 null을 반환합니다. isas는 naked generic 형식 매개 변수와 특정 매개 변수가 있는 generic 클래스에서 모두 사용할 수 있습니다.

코드 블록 6. generic 형식 매개 변수에서 'is' 및 'as' 연산자 사용

public class MyClass<T> 
{
   public void SomeMethod(T t)
   {
      if(t is int)
      {...} 

      if(t is LinkedList<int,string>)
      {...}

      string str = t as string;
      if (drv != null)
      {...}

      LinkedList<int,string> list = t as LinkedList<int,string>;
      if(list != null)
      {...}
   }
}

상속 및 generics

generic 기본 클래스로부터 파생할 경우 기본 클래스의 generic 형식 매개 변수 대신 특정 형식을 제공해야 합니다.

public class BaseClass<T>
{...}
public class SubClass : BaseClass<int>
{...}

하위 클래스가 구체적인 형식이 아니라 generic인 경우 subclass generic 형식 매개 변수를 generic 기본 클래스의 지정된 형식으로 사용할 수 있습니다.

public class SubClass<T> : BaseClass<T> 
{...}

subclass generic 형식 매개 변수를 사용할 때는 하위 클래스 수준의 기본 클래스 수준에서 규정된 모든 제약 조건을 반복해야 합니다. 예를 들어 파생 제약 조건은 다음과 같습니다.

public class BaseClass<T>  where T : ISomeInterface 
{...}
public class SubClass<T> : BaseClass<T> where T : ISomeInterface
{...}

생성자 제약 조건은 다음과 같습니다.

public class BaseClass<T>  where T : new()
{   
   public T SomeMethod()
   {
      return new T();
   }
}
public class SubClass<T> : BaseClass<T> where T : new() 
{...}

기본 클래스는 서명이 generic 형식 매개 변수를 사용하는 가상 메서드를 정의할 수 있습니다. 가상 메서드를 다시 정의할 경우 하위 클래스는 메서드 서명에서 해당 형식을 제공해야 합니다.

public class BaseClass<T>
{ 
   public virtual T SomeMethod()
   {...}
}
public class SubClass: BaseClass<int>
{ 
   public override int SomeMethod()
   {...}
}

하위 클래스가 generic이면 자체 generic 형식 매개 변수를 사용하여 다시 정의할 수도 있습니다.

public class SubClass<T>: BaseClass<T>
{ 
   public override T SomeMethod()
   {...}
}

generic 인터페이스, generic 추상 클래스 및 generic 추상 메서드도 정의할 수 있습니다. 이러한 형식은 다른 generic 기본 형식과 마찬가지로 동작합니다.

public interface ISomeInterface<T>
{
   T SomeMethod(T t);
}
public abstract class BaseClass<T>
{
   public abstract T SomeMethod(T t);
}

public class SubClass<T> : BaseClass<T>
{
   public override T SomeMethod(T t) 
   {...)
}

generic 추상 메서드 및 generic 인터페이스의 흥미로운 사용 방법이 있습니다. C# 2.0에서는 generic 형식 매개 변수에 + 또는 += 같은 연산자를 사용할 수 없습니다. 예를 들어 C# 2.0에는 연산자 제약 조건이 없기 때문에 다음 코드는 컴파일되지 않습니다.

public class Calculator<T>
{
   public T Add(T arg1,T arg2)
   {
      return arg1 + arg2;//컴파일되지 않습니다.
   }
   //메서드의 나머지 부분
}

그러나 이 문제는 generic 연산을 정의하여 추상 메서드 또는 추상 인터페이스(선호됨)를 사용함으로써 보완할 수 있습니다. 추상 메서드에는 코드가 있을 수 없으므로 기본 클래스 수준에서 generic 연산을 지정하고 하위 클래스 수준에서 구체적인 형식 및 구현을 제공하면 됩니다.

public abstract class BaseCalculator<T>
{
   public abstract T Add(T arg1,T arg2);
   public abstract T Subtract(T arg1,T arg2);
   public abstract T Divide(T arg1,T arg2);
   public abstract T Multiply(T arg1,T arg2);
}
public class MyCalculator : BaseCalculator<int>
{
   public override int Add(int arg1, int arg2)
   {
      return arg1 + arg2;
   }
   //메서드의 나머지 부분
} 

generic 인터페이스는 좀 더 나은 솔루션을 제공합니다.

public interface ICalculator<T>
{
   T Add(T arg1,T arg2);
   //메서드의 나머지 부분
}
public class MyCalculator : ICalculator<int>
{
   public int Add(int arg1, int arg2)
   {
      return arg1 + arg2;
   }
   //메서드의 나머지 부분
}

generic 메서드

C# 2.0에서 메서드는 해당 실행 범위에 고유한 generic 형식 매개 변수를 정의할 수 있습니다.

public class MyClass<T>
{
   public void MyMethod<X>(X x)
   {...}
}

이는 매번 다른 형식으로 메서드를 호출할 수 있도록 하는 중요한 기능으로, 유틸리티 클래스에 특히 편리합니다.

포함하는 클래스가 generics를 전혀 사용하지 않는 경우에도 메서드별 generic 형식 매개 변수를 정의할 수 있습니다.

public class MyClass
{
   public void MyMethod<T>(T t)
   {...}
}

이 기능은 메서드 전용입니다. 속성이나 인덱서는 클래스 범위에 정의된 generic 형식 매개 변수만 사용할 수 있습니다.

generic 형식 매개 변수를 정의하는 메서드를 호출할 경우 호출 지점에서 사용할 형식을 제공할 수 있습니다.

MyClass obj = new MyClass();
obj.MyMethod<int>(3);

즉, C# 컴파일러는 메서드가 호출되면 전달된 매개 변수의 형식을 기준으로 올바른 형식을 자동으로 유추할 수 있으며, 형식 지정을 전부 생략할 수도 있습니다.

MyClass obj = new MyClass();
obj.MyMethod(3);

이 기능을 generic 형식 유추라고 합니다. 컴파일러가 반환된 값의 형식만을 기준으로 형식을 유추할 수는 없습니다.

public class MyClass
{
   public T MyMethod<T>()
   {}
}
MyClass obj = new MyClass();
int number = obj.MyMethod();//컴파일되지 않습니다.

메서드는 자체 generic 형식 매개 변수를 정의할 때 해당 형식에 대한 제약 조건도 정의할 수 있습니다.

public class MyClass
{
   public void SomeMethod<T>(T t) where T : IComparable<T> 
   {...}
}

그러나 클래스 수준 generic 형식 매개 변수에 메서드 수준 제약 조건을 제공할 수는 없습니다. 클래스 수준 generic 형식 매개 변수의 모든 제약 조건은 클래스 범위에서 정의되어야 합니다.

generic 형식 매개 변수를 정의하는 가상 메서드를 다시 정의할 경우 하위 클래스 메서드는 메서드별 generic 형식 매개 변수를 다시 정의해야 합니다.

public class BaseClass
{
   public virtual void SomeMethod<T>(T t)
   {...}
}
public class SubClass : BaseClass
{
   public override void SomeMethod<T>(T t)
   {...}
}

하위 클래스 구현에서는 기본 메서드 수준에 나타나는 모든 제약 조건을 반복해야 합니다.

public class BaseClass
{
   public virtual void SomeMethod<T>(T t) where T : new()
   {...}
}
public class SubClass : BaseClass
{
   public override void SomeMethod<T>(T t) where T : new()
   {...}
}

메서드를 다시 정의해도 기본 메서드에 나타나지 않는 새 제약 조건은 정의할 수 없습니다.

또한 하위 클래스 메서드가 가상 메서드의 기본 클래스 구현을 호출하는 경우 generic 기본 메서드 형식 매개 변수 대신 사용할 형식을 지정해야 합니다. 직접 명시적으로 지정하거나 가능한 경우 형식 유추를 사용할 수 있습니다.

public class BaseClass
{ 
   public virtual void SomeMethod<T>(T t)
   {...}
}
public class SubClass : BaseClass
{
   public override void SomeMethod<T>(T t)
   {
      base.SomeMethod<T>(t);
      base.SomeMethod(t);
   }
}

generic 정적 메서드

C#에서는 generic 형식 매개 변수를 사용하는 정적 메서드를 정의할 수 있습니다. 그러나 이러한 정적 메서드를 호출할 경우 이 예제에서와 같이 호출 지점에서 포함하는 클래스에 대한 구체적인 형식을 제공해야 합니다.

public class MyClass<T>
{
   public static T SomeMethod(T t)
   {...}
}
int number = MyClass<int>.SomeMethod(3); 

정적 메서드는 인스턴스 메서드와 마찬가지로 메서드별 generic 형식 매개 변수 및 제약 조건을 정의할 수 있습니다. 이러한 메서드를 호출할 경우 호출 지점에서 메서드별 형식을 다음과 같이 명시적으로 제공해야 합니다.

public class MyClass<T>
{
   public static T SomeMethod<X>(T t,X x)
   {..}
}
int number = MyClass<int>.SomeMethod<string>(3,"AAA");

또는 가능한 경우 형식 유추를 사용할 수 있습니다.

int number = MyClass<int>.SomeMethod(3,"AAA");

generic 정적 메서드는 클래스 수준에서 사용 중인 generic 형식 매개 변수에 적용되는 모든 제약 조건을 따릅니다. 인스턴스 메서드와 마찬가지로 정적 메서드가 정의한 generic 형식 매개 변수에 제약 조건을 제공할 수 있습니다.

public class MyClass
{
   public static T SomeMethod<T>(T t) where T : IComparable<T> 
   {...}
}

C#의 연산자는 단지 정적 메서드일 뿐이며 C#에서는 generic 형식에 연산자를 오버로드할 수 있습니다. 코드 블록 3의 generic LinkedList가 + 연산자를 제공하여 연결 리스트를 연결했다고 가정하십시오. + 연산자를 사용하여 다음과 같이 적절한 코드를 작성할 수 있습니다.

LinkedList<int,string> list1 = new LinkedList<int,string>();
LinkedList<int,string> list2 = new LinkedList<int,string>();
 ...
LinkedList<int,string> list3 = list1+list2;

코드 블록 7LinkedList 클래스에서 generic + 연산자의 구현을 보여 줍니다. 연산자는 새 generic 형식 매개 변수를 정의할 수 없습니다.

코드 블록 7. generic 연산자 구현

public class LinkedList<K,T>
{
   public static LinkedList<K,T> operator+(LinkedList<K,T> lhs,
                                           LinkedList<K,T> rhs)
   {
      return concatenate(lhs,rhs);
   }
   static LinkedList<K,T> concatenate(LinkedList<K,T> list1,
                                      LinkedList<K,T> list2)
   {
      LinkedList<K,T> newList = new LinkedList<K,T>(); 
      Node<K,T> current;
      current = list1.m_Head;
      while(current != null)
      {
         newList.AddHead(current.Key,current.Item);
         current = current.NextNode;
      }
      current = list2.m_Head;
      while(current != null)
      {
         newList.AddHead(current.Key,current.Item);
         current = current.NextNode;
      }
      return newList;
   }
   //LinkedList의 나머지 부분
}

generic 위임

클래스에 정의된 위임은 해당 클래스의 generic 형식 매개 변수를 활용할 수 있습니다. 다음 예를 살펴봅시다.

public class MyClass<T>
{
   public delegate void GenericDelegate(T t);
   public void SomeMethod(T t)
   {...} 
}

포함하는 클래스의 형식을 지정할 경우 위임에도 영향을 미칩니다.

MyClass<int> obj = new MyClass<int>();
MyClass<int>.GenericDelegate del;

del = new MyClass<int>.GenericDelegate(obj.SomeMethod);
del(3);

C# 2.0에서는 메서드 참조를 위임 변수에 직접 할당할 수 있습니다.

MyClass<int> obj = new MyClass<int>();
MyClass<int>.GenericDelegate del;

del = obj.SomeMethod;

이 기능을 위임 유추라고 합니다. 컴파일러는 지정된 이름을 기준으로 대상 개체에 메서드가 있는지 찾고 메서드의 서명 일치를 확인하여 할당된 위임의 형식을 유출할 수 있습니다. 그런 다음 컴파일러는 유추한 인수 형식(generic 형식 매개 변수 대신 올바른 형식 포함)의 새 위임을 만들어 유추된 위임에 할당할 수 있습니다.

클래스, 구조체 및 메서드와 마찬가지로 위임도 generic 형식 매개 변수를 정의할 수 있습니다.

public class MyClass<T>
{
   public delegate void GenericDelegate<X>(T t,X x);
}

클래스 범위 외부에 정의된 위임은 generic 형식 매개 변수를 사용할 수 있습니다. 이 경우 위임을 선언하고 인스턴스화할 때 위임에 대한 특정 형식을 제공해야 합니다.

public delegate void GenericDelegate<T>(T t);

public class MyClass
{
   public void SomeMethod(int number)
   {...}
}

MyClass obj = new MyClass();
GenericDelegate<int> del; 

del = new GenericDelegate<int>(obj.SomeMethod);
del(3);

또는 위임을 할당할 때 위임 유추를 사용할 수 있습니다.

MyClass obj = new MyClass();
GenericDelegate<int> del; 

del = obj.SomeMethod;

기본적으로 위임은 generic 형식 매개 변수와 함께 사용될 제약 조건을 정의할 수 있습니다.

public delegate void MyDelegate<T>(T t) where T : IComparable<T>;

위임 수준 제약 조건은 형식 또는 메서드 범위에 있는 다른 모든 제약 조건과 마찬가지로 위임 변수를 선언하고 위임 개체를 인스턴스화할 때 사용하는 쪽에서만 적용됩니다.

generic 위임은 이벤트의 경우 특히 유용합니다. 문자 그대로, 필요한 generic 형식 매개 변수의 수에 의해서만 구분되는 generic 위임의 제한된 집합을 정의하고 이러한 위임을 이벤트 처리 작업에서 필요한 모든 곳에 사용할 수 있습니다. 코드 블록 8은 generic 위임 및 generic 이벤트 처리 메서드의 사용을 보여 줍니다.

코드 블록 8. generic 이벤트 처리

public delegate void GenericEventHandler<SenderType,ArgsType>(SenderType sender,
                                                              ArgsType args);
public class MyPublisher
{
   public event GenericEventHandler<MyPublisher,EventArgs> MyEvent;
   public void FireEvent()
   {
      MyEvent(this,EventArgs.Empty);
   }
}
public class MySubscriber<ArgsType> //선택적: 특정 형식일 수 있습니다.
{
   public void SomeMethod(MyPublisher sender,ArgsType args)
   {...}
}
MyPublisher publisher = new MyPublisher();
MySubscriber<EventArgs> subs = new MySubscriber<EventArgs>();
publisher.MyEvent += subs.SomeMethod;

코드 블록 8은 generic sender 형식과 generic 형식 매개 변수를 받아들이는 GenericEventHandler라는 generic 위임을 사용합니다. 물론 매개 변수가 더 필요한 경우 generic 형식 매개 변수를 추가하기만 하면 되지만 GenericEventHandler를 generic이 아닌 .NET EventHandler 뒤에 모델링하고자 합니다. EventHandler는 다음과 같이 정의되어 있습니다.

public void delegate EventHandler(object sender,EventArgs args);

EventHandler와 달리 GenericEventHandler는 형식이 안전합니다. 코드 블록 8에 나와 있듯이 일반 개체가 아니라 MyPublisher 형식의 개체만 발신자로 받아들이기 때문입니다.

generics 및 반사

.NET 2.0에서는 반사가 generic 형식 매개 변수를 지원하도록 확장되었습니다. 이제 Type 형식은 특정 형식 매개 변수가 있는 generic 형식(제한된(bounded) 형식) 또는 지정되지 않은(제한되지 않은(unbounded)) 형식을 나타낼 수 있습니다. C# 1.1에서와 같이 typeof 연산자를 사용하거나 모든 형식이 지원하는 GetType() 메서드를 호출하여 모든 형식의 Type을 얻을 수 있습니다. 선택한 방법에 관계없이 둘 다 동일한 Type이 나옵니다. 예를 들어 다음 코드 예제에서는 type1이 type2와 동일합니다.

LinkedList<int,string> list = new LinkedList<int,string>();

Type type1 = typeof(LinkedList<int,string>);
Type type2 = list.GetType();
Debug.Assert(type1 == type2);

typeofGetType() 모두 naked generic 형식 매개 변수에서 사용할 수 있습니다.

public class MyClass<T> 
{
   public void SomeMethod(T t)
   {
      Type type = typeof(T);
      Debug.Assert(type == t.GetType());
   }
}

Type에는 형식의 generic 측면에 대한 반사 정보를 제공하도록 만들어진 새로운 메서드와 속성이 있습니다. 코드 블록 9는 새로운 메서드를 보여 줍니다.

코드 블록 9. Type의 generic 반사 멤버

public abstract class Type : //기본 형식입니다.
{
   public int  GenericParameterPosition{virtual get;} 
   public bool HasGenericParameters{get;}
   public bool HasUnboundGenericParameters{virtual get;}            
   public bool IsGenericParameter{virtual get;}
   public bool IsGenericTypeDefinition{virtual get;}
   public virtual Type BindGenericParameters(Type[] typeArgs);
   public virtual Type[] GetGenericParameters();
   public virtual Type GetGenericTypeDefinition();
   //멤버의 나머지 부분
}

이 새 멤버 중에서 가장 유용한 것은 HasGenericParametersHasUnboundGenericParameters 속성과 GetGenericParameters()GetGenericTypeDefinition() 메서드입니다. Type의 나머지 새 멤버는 고급 시나리오 및 이 기사의 범위를 벗어나는 다소 난해한 시나리오를 위한 것입니다. 이름에서 나타나듯이 HasGenericParameters는 Type 개체가 나타내는 형식이 generic 형식 매개 변수를 사용할 경우 true로 설정됩니다. GetGenericParameters()는 사용되는 제한된 형식에 해당하는 Type의 배열을 반환합니다. GetGenericTypeDefinition()은 원본 형식의 generic 형태를 나타내는 Type을 반환합니다. HasUnboundGenericParameters는 지정되지 않은 generic 형식 매개 변수가 Type 개체에 있는 경우 true입니다. 또한 HasUnboundGenericParameters는 generic 형식에서 호출될 때 GetGenericTypeDefinition()에서 반환된 Type에서 true로 설정됩니다.

코드 블록 10은 이러한 새 Type 멤버를 사용하여 코드 블록 3LinkedList에 대한 generic 반사 정보를 얻는 방법을 보여 줍니다.

코드 블록 10. generic 반사에 Type 사용

LinkedList<int,string> list = new LinkedList<int,string>();

Type boundedType = list.GetType();
Trace.WriteLine(boundedType.ToString());
//'LinkedList[System.Int32,System.String]'을 씁니다.

Debug.Assert(boundedType.HasGenericParameters);

Type[] parameters = boundedType.GetGenericParameters();

Debug.Assert(parameters.Length == 2);
Debug.Assert(parameters[0] == typeof(int));
Debug.Assert(parameters[1] == typeof(string));

Type unboundedType = boundedType.GetGenericTypeDefinition();
Debug.Assert(unboundedType.HasUnboundGenericParameters);
Trace.WriteLine(unboundedType.ToString());
//'LinkedList[K,T]'를 씁니다.

코드 블록 10에 나와 있듯이 Type은 제한된 매개 변수가 있는 generic 형식(코드 블록 10boundedType) 또는 제한되지 않은 매개 변수가 있는 generic 형식(코드 블록 10unboundedType)을 나타낼 수 있습니다.

Type과 유사하게 MethodInfo 및 해당 기본 클래스 MethodBase에는 generic 메서드 정보를 반사하는 새 멤버가 있습니다.

C# 1.1에서와 마찬가지로 MethodInfo 및 기타 다수의 옵션을 사용하여 런타임에 바인딩을 호출할 수 있습니다. 그러나 런타임에 바인딩에 대해 전달하는 매개 변수의 형식은 generic 형식 매개 변수 대신 사용되는 제한된 형식(있을 경우)과 일치해야 합니다.

LinkedList<int,string> list = new LinkedList<int,string>();
Type type = list.GetType();
MethodInfo methodInfo = type.GetMethod("AddHead");
object[] args = {1,"AAA"};
methodInfo.Invoke(list,args);

특성 및 generics

특성을 정의할 때 열거 AttributeTargets의 새 GenericParameter 값을 사용하여 특성이 generic 형식 매개 변수를 대상으로 하도록 컴파일러에 지정할 수 있습니다.

[AttributeUsage(AttributeTargets.GenericParameter)] 
public class SomeAttribute : Attribute
{...}

C# 2.0에서는 generic 특성을 정의할 수 없습니다.

//다음은 컴파일되지 않습니다.
public class SomeAttribute<T> : Attribute 
{...}

그러나 내부적으로 특성 클래스는 generic 형식을 사용하여 generics를 활용하거나 도우미 generic 메서드를 다른 형식과 마찬가지로 정의할 수 있습니다.

public class SomeAttribute : Attribute
{
   void SomeMethod<T>(T t)
   {...}
   LinkedList<int,string> m_List = new LinkedList<int,string>();
}

generics 및 .NET Framework

.NET에서 C# 자체를 제외한 다른 일부 영역에서는 generics를 어떻게 활용하고 generics와 어떻게 상호 작용하는지 살펴보면서 이 기사를 마치겠습니다.

generic 컬렉션

System.Collections의 데이터 구조는 모두 개체 기반이므로 이 기사의 첫 부분에서 설명한 두 가지 문제, 즉 낮은 성능과 형식 안전성 부족의 문제가 계속해서 나타납니다. .NET 2.0에서는 System.Collections.Generics 네임스페이스에 새로운 generic 컬렉션 집합을 사용합니다. 예를 들어 generic Stack<T> 및 generic Queue<T> 클래스가 있습니다. Dictionary<K,T> 데이터 구조는 generic이 아닌 HashTable과 동등하며 SortedList와 다소 비슷한 SortedDictionary<K,T> 클래스도 있습니다. List<T> 클래스는 generic이 아닌 ArrayList와 유사합니다. 표 1은 System.Collections.Generics의 새로운 형식을 System.Collections의 형식에 대응시킨 것입니다.

표 1. System.Collections.Generics를 System.Collections에 대응

System.Collections.Generics System.Collections
Comparer<T> Comparer
Dictionary<K,T> HashTable
List<T> ArrayList
Queue<T> Queue
SortedDictionary<K,T> SortedList
Stack<T> Stack
ICollection<T> ICollection
IComparable<T> System.IComparable
IComparer<T> IComparer
IDictionary<K,T> IDictionary
IEnumerable<T> IEnumerable
IEnumerator<T> IEnumerator
IKeyComparer<T> IKeyComparer
IList<T> IList

System.Collections.Generics에 있는 모든 generic 컬렉션은 다음과 같이 정의된 generic IEnumerable<T> 인터페이스도 구현합니다.

public interface IEnumerable<T>
{
   IEnumerator<T> GetEnumerator();
} 
public interface IEnumerator<T> : IDisposable
{
   T Current{get;}
   bool MoveNext();
}

요약하자면 IEnumerable<T>는 컬렉션에서 추상적 반복에 사용되는 IEnumerator<T> iterator 인터페이스에 액세스할 수 있도록 합니다. 모든 컬렉션은 내포된 구조체에서 IEnumerable<T>를 구현하고, 내포된 구조체에서 generic 형식 매개 변수 T는 컬렉션이 저장하는 형식입니다.

관심이 가는 부분은 사전 컬렉션이 iterator를 정의하는 방법입니다. 사전은 실제로 하나가 아닌 두 가지 generic 매개 변수 형식(키 및 값)의 컬렉션입니다. System.Collection.Generics는 다음과 같이 정의된 KeyValuePair<K,V>라는 generic 구조체를 제공합니다.

struct KeyValuePair<K,V>
{
   public KeyValuePair(K key,V value);
   public K Key(get;set;)
   public V Value(get;set;)
}

KeyValuePair<K,V>는 단순히 generic 키와 generic 값의 쌍을 저장할 뿐이며, 그럼으로써 실제로 새로운 generic 형식을 정의합니다. 이 새 형식은 사전에서 컬렉션으로 관리되며 IEnumerable<T>를 구현하는 데 사용됩니다. Dictionary 클래스는 generic KeyValuePair<K,V> 구조를 IEnumerable<T>ICollection<T>의 항목 형식으로 지정합니다.

public class Dictionary<K,T> : IEnumerable<KeyValuePair<K,T>>, 
                               ICollection<KeyValuePair<K,T>>, 
                               //추가 인터페이스
{...} 

KeyValuePair<K,V>에 사용되는 키 및 값 형식 매개 변수는 물론 사전 자체의 generic 키 및 값 형식 매개 변수입니다. 이 기술을 복합 generics라고 합니다.

키 및 값 쌍을 사용하는 사용자 고유의 generic 데이터 구조에서 복합 generics를 확실히 활용할 수 있습니다. 다음 예를 살펴봅시다.

public class LinkedList<K,T> : IEnumerable<KeyValuePair<K,T>> where K : IComparable<K>
{...} 

serialization 및 generics

.NET에서는 serializable generic 형식을 사용할 수 있습니다.

[Serializable]
public class MyClass<T>
{...}

형식을 serialize할 때 .NET은 개체 멤버의 상태 외에도 개체 및 그 형식에 대한 메타데이터를 유지합니다. serializable 형식이 generic이고 제한된 형식을 포함하는 경우 제한된 형식에 대한 형식 정보도 generic 형식에 대한 메타데이터에 포함됩니다. 따라서 특정 인수 형식이 있는 generic 형식의 각 순열은 고유한 형식으로 간주됩니다. 예를 들어 개체 형식 MyClass<int>를 serialize할 수는 없지만 MyClass<string> 형식의 개체로 deserialize할 수는 있습니다. generic 형식의 인스턴스를 serialize하는 것은 generic이 아닌 형식을 serialize하는 것과 다르지 않습니다. 그러나 해당 형식을 deserialize할 경우에는 일치하는 특정 형식으로 변수를 선언하고 deserialize로부터 반환된 개체를 다운 캐스팅할 때 해당 형식을 다시 지정해야 합니다. 코드 블록 11은 generic 형식의 serialization 및 deserialization을 보여 줍니다.

코드 블록 11. generic 형식의 클라이언트 쪽 serialization

[Serializable]
public class MyClass<T>
{...}
MyClass<int> obj1 = new MyClass<int>();

IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("obj.bin",FileMode.Create,FileAccess.ReadWrite); 
formatter.Serialize(stream,obj1);
stream.Seek(0,SeekOrigin.Begin);

MyClass<int> obj2; 
obj2 = (MyClass<int>)formatter.Deserialize(stream);
stream.Close(); 

사용자 지정 serialization을 제공하는 경우 deserialization 동안 값을 얻을 수도 있는 SerializationInfo라는 속성 모음에 값을 추가해야 합니다. serialization에 사용된 SerializationInfo를 제공하는 단일 메서드 GetObjectData()가 있는 ISerializable 인터페이스를 구현하십시오. 또한 개체 상태를 가져올 때 사용하는 SerializationInfo를 받아들이는 특수 생성자를 구현해야 합니다.

SerializationInfo는 필드 값을 가져오거나 추가할 수 있는 메서드를 제공합니다. 각 필드는 문자열로 식별됩니다. SerializationInfo에는 int 및 string과 같은 대부분의 CLR 기본 형식에 대해 형식이 안전한 메서드가 있습니다.

public sealed class SerializationInfo 
{
   public void AddValue(string name, int value);
   public int  GetInt32(string name);
   //기타 메서드 및 속성
}

generic 형식에 사용자 지정 serialization을 제공할 때의 문제는 추가 메서드 또는 가져오기 메서드 중에서 어느 것을 사용해야 할지 모른다는 것입니다. 이 문제를 해결하기 위해 SerializationInfo는 개체 및 그 형식을 추가하거나 가져올 수 있는 메서드를 제공합니다.

public void AddValue(string name, object value, Type type);
public object GetValue(string name,Type type);
참고    이 메서드는 .NET 1.1에서도 사용할 수 있습니다.

AddValue()를 사용할 경우 generic 매개 변수 형식의 형식을 가져옵니다. GetValue()를 호출할 경우에는 GetValue()가 개체를 반환하므로 generic 매개 변수 형식으로의 캐스트를 사용합니다. 코드 블록 12는 이러한 AddValue()GetValue() 메서드의 사용을 보여 줍니다.

코드 블록 12. generic 클래스의 사용자 지정 serialization

[Serializable]
public class MyClass<T> : ISerializable
{
   public MyClass()
   {}
   public void GetObjectData(SerializationInfo info,StreamingContext ctx)
   {
      info.AddValue("m_T",m_T,typeof(T));
   }
   private MyClass(SerializationInfo info,StreamingContext context)
   {
      m_T = (T)info.GetValue("m_T",typeof(T));
   }
   T m_T;
}

그러나 generic 형식에 좀 더 효율적이고 안전하게 사용자 지정 serialization을 제공할 수 있는 방법이 있습니다. 코드 블록 13은 generic AddValue()GetValue() 메서드를 노출하는 GenericSerializationInfo 유틸리티 클래스를 나타냅니다. 일반 SerializationInfo를 캡슐화함으로써 GenericSerializationInfo는 형식 검색 및 명시적 캐스팅으로부터 클라이언트 코드를 보호합니다.

코드 블록 13. GenericSerializationInfo 유틸리티 클래스

public class GenericSerializationInfo
{
   SerializationInfo m_SerializationInfo;
   public GenericSerializationInfo(SerializationInfo info)
   {
      m_SerializationInfo = info;
   }
   public void AddValue<T>(string name,T value)
   {
      m_SerializationInfo.AddValue(name,value,value.GetType());
   }
   public T GetValue<T>(string name)
   {
      object obj = m_SerializationInfo.GetValue(name,typeof(T));
      return (T)obj;
   }
}

코드 블록 14GenericSerializationInfo를 사용한다는 것만 제외하고는 코드 블록 12와 동일한 사용자 지정 serialization 코드를 보여 줍니다. AddValue() 호출에서 형식 유추가 사용되는 것을 볼 수 있습니다.

코드 블록 14. GenericSerializationInfo 사용

[Serializable]
public class MyClass<T> : ISerializable
{
   public MyClass()
   {}
   public void GetObjectData(SerializationInfo info,StreamingContext ctx)
   {
      GenericSerializationInfo genericInfo = new GenericSerializationInfo(info);

      genericInfo.AddValue("m_T",m_T); //형식 유추 사용 
   }
   private MyClass(SerializationInfo info,StreamingContext context)
   {
      GenericSerializationInfo genericInfo = new GenericSerializationInfo(info);

      m_T = genericInfo.GetValue<T>("m_T");
   }
   T m_T;
}

generic이 아닌 클래스 멤버에서도 GenericSerializationInfo를 좀 더 쉬운 사용자 지정 serialization 구현 방법으로 사용할 수 있습니다. 예를 들어 코드 블록 14MyClass에 m_Name이라는 형식 문자열의 클래스 멤버가 있으면 다음과 같이 코드를 작성할 수 있습니다.

genericInfo.AddValue("m_Name",m_Name);

m_Name = genericInfo.GetValue<string>("m_Name");

generics 및 원격

generics를 활용하는 원격 클래스를 정의하고 배포할 수 있으며, 프로그래밍 방식의 구성 또는 관리 구성을 사용할 수 있습니다. generics를 사용하는 MyServer 클래스가 MarshalByRefObject에서 파생된다고 가정해 보십시오.

public class MyServer<T> : MarshalByRefObject
{...}

관리 형식 등록을 사용하는 경우 generic 형식 매개 변수 대신 사용할 정확한 형식을 지정해야 합니다. 언어 중립적인 방법으로 형식의 이름을 지정하고 정규화된 네임스페이스를 제공해야 합니다. 예를 들어 ServerAssembly 어셈블리의 RemoteServer 네임스페이스에 정의되어 있는 MyServer 클래스를 클라이언트 활성화 모드에서 generic 형식 매개 변수 T 대신 정수와 함께 사용하려고 합니다. 이 경우 구성 파일에서 필요한 클라이언트 쪽 형식 등록 항목은 다음과 같습니다.

<client url="...some url goes here..."> 
   <activated type="RemoteServer.MyServer[[System.Int32]],ServerAssembly"/>
</client>

구성 파일에서 대응하는 호스트 쪽 형식 등록 항목은 다음과 같습니다.

<service>
   <activated type="RemoteServer.MyServer[[System.Int32]],ServerAssembly"/> 
</service>

이중 대괄호는 복수 형식을 지정하는 데 사용됩니다. 다음 예를 살펴봅시다.

LinkedList[[System.Int32],[System.String]]

프로그래밍 방식 구성을 사용하는 경우 generic 형식 매개 변수 대신 특정 형식을 제공해야 하는 원격 개체의 형식을 정의할 때를 제외하고는 C# 1.1과 비슷한 활성화 모드 및 형식 등록을 구성합니다. 예를 들어 호스트 쪽 활성화 모드 및 형식 등록에 대해 다음과 같이 씁니다.

Type serverType = typeof(MyServer<int>);
RemotingConfiguration.RegisterActivatedServiceType(serverType);

클라이언트 쪽 형식 활성화 모드 및 위치 등록에 대해서는 다음과 같이 씁니다.

Type serverType = typeof(MyServer<int>);      
string url = ...; //일부 url 초기화 
RemotingConfiguration.RegisterWellKnownClientType(serverType,url);

원격 서버를 인스턴스화하는 경우 로컬 generic 형식을 사용할 때처럼 특정 형식을 제공하기만 하면 됩니다.

MyServer<int> obj;
obj = new MyServer<int>();
//obj 사용

클라이언트는 새 메서드를 사용하는 대신 Activator 클래스의 메서드를 선택적으로 사용하여 원격 개체에 연결할 수 있습니다. Activator.GetObject()를 사용할 때는 사용할 특정 형식을 제공해야 하며 반환된 개체를 명시적으로 캐스팅할 때는 특정 형식을 제공해야 합니다.

string url = ...; //일부 url 초기화 
Type serverType = typeof(MyServer<int>);
MyServer<int> obj;
obj = (MyServer<int>)Activator.GetObject(serverType,url);
//obj 사용

Activator.CreateInstance() 메서드에는 수많은 오버로드 버전이 있습니다. 그 중 일부 버전을 generic 형식에 사용할 수 있습니다.

Type serverType = typeof(MyServer<int>);
MyServer<int> obj;
obj = (MyServer<int>)Activator.CreateInstance(serverType);
//obj 사용

그러나 generic 형식에는 다음과 같이 문자열 형식을 받아들이고 ObjectHandle을 반환하는 CreateInstance()CreateInstanceFrom()의 버전을 사용할 수 없습니다.

public static ObjectHandle CreateInstance(string assemblyName,string typeName);

Activator는 generics를 고려하지 않고 만들었기 때문에 개체로부터의 명시적인 캐스트로 인해 형식이 완전히 안전하지는 않습니다. 코드 블록 15에서와 같이 GenericActivator 클래스를 정의하여 이를 보완할 수 있습니다.

코드 블록 15. GenericActivator

public class GenericActivator
{
   public static T CreateInstance<T>()
   {
      return (T)Activator.CreateInstance(typeof(T));
   }
   public static T GetObject<T>(string url)
   {
      return (T)Activator.GetObject(typeof(T),url);
   }
 }

GenericActivator는 generic 정적 메서드를 사용하여 Activator의 가장 유용한 메서드를 노출합니다. GenericActivatorActivator 대신 사용하여 generic 형식 및 generic이 아닌 형식을 활성화할 수 있습니다. 예를 들어 GetObject()를 사용합니다.

public class MyServer : MarshalByRefObject
{...}
public class MyGenericServer<T> : MarshalByRefObject
{...}

string url = ...; //일부 url 초기화 
MyServer obj1 = GenericActivator.GetObject<MyServer>(url);
MyGenericServer obj2 = GenericActivator.GetObject<MyServer<int>>(url);

또는 CreateInstance()를 사용합니다.

MyServer obj = GenericActivator.CreateInstance<MyServer>();

generics에서 불가능한 작업

.NET 2.0에서는 generic 웹 서비스, 즉, generic 형식 매개 변수를 사용하는 웹 메서드를 정의할 수 없습니다. generic 서비스를 지원하는 웹 서비스 표준이 없기 때문입니다.

서비스되는 구성 요소에서도 generic 형식을 사용할 수 없습니다. 서비스되는 구성 요소에 필요한 COM 표시 유형 요구 사항을 generics가 충족하지 못하기 때문입니다. COM 또는 COM+에서 C++ 템플릿을 사용할 수 없는 것과 마찬가지입니다.

결론

C# generics는 새로운 주요 개발 방법으로서 적절하고 읽기 쉬운 구문을 사용하여 성능, 형식 안전성 및 품질을 향상시키고, 반복적인 프로그래밍 작업을 줄이고, 전체 프로그래밍 모델을 단순화합니다. C# generics는 C++ 템플릿을 기반으로 하고 있지만 C#는 코드 비대화를 없애고 컴파일 시 안전성 및 지원을 제공함으로써 generics의 수준을 한 차원 높였습니다. C#는 2단계 컴파일, 메타데이터, 제약 조건 및 generic 메서드와 같은 혁신적인 개념을 활용합니다. C#의 향후 버전이 generics를 지속적으로 발전시켜 새로운 기능을 추가하고 generics를 데이터 액세스 또는 지역화 등 .NET Framework의 다른 영역까지 확장할 것은 분명합니다.


Juval Lowy는 소프트웨어 개발자이자 컨설팅 및 교육 회사인 IDesign의 사장입니다. Juval은 실리콘 밸리의 Microsoft 지역 담당으로서 Microsoft가 .NET을 업계에 보급하는 데 협력하고 있습니다. 최근의 저서는 Programming .NET Components(O'Reilly, 2003)입니다. 이 책은 구성 요소 지향 프로그램 및 디자인, 관련 시스템 문제에 대해 다루고 있습니다. Juval은 .NET의 향후 버전에 대한 Microsoft 내부 디자인 검토에 참여하고 있습니다. Microsoft에서는 Juval을 소프트웨어 업계의 전설적인 존재이자 세계 최고의 .NET 전문가 및 업계 선도자 중 한 명으로 인정하고 있습니다. http://www.idesign.net/ 에서 Juval을 만나실 수 있습니다.

출처:http://www.microsoft.com/korea

posted by 방랑군 2009. 8. 24. 11:30
..
 Generics 간단 예제.

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 Main(string[] args)

016    {

017         // 일반정수좌표

018         Point<int> intPoint;

019         intPoint.X = 1;

020         intPoint.Y = 2;

021         Console.WriteLine("정수형 Point 좌표: X({0}), Y좌표({1})", intPoint.X, intPoint.Y);

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 }

 

'IT > C#' 카테고리의 다른 글

C#2.0 Iterators,Partial 클래스,Nullable 타입  (0) 2009.10.07
닷넷 트랜잭션 정리  (0) 2009.09.30
C#2.0 , C# 3.0 New Features  (0) 2009.09.15
C# 3.0 개요  (0) 2009.09.11
[.net Framework] System.Activator 객체  (0) 2009.08.24
posted by 방랑군 2009. 8. 24. 11:11

System.Activator

:

'IT > C#' 카테고리의 다른 글

C#2.0 Iterators,Partial 클래스,Nullable 타입  (0) 2009.10.07
닷넷 트랜잭션 정리  (0) 2009.09.30
C#2.0 , C# 3.0 New Features  (0) 2009.09.15
C# 3.0 개요  (0) 2009.09.11
[C# 2.0] Generics, Iterator, Anonymous, Partial  (0) 2009.08.24
IT
posted by 방랑군 2009. 7. 30. 08:15

1. 대화형 로그온 : [Ctrl+Alt+Del] 사용 없이 로그온(2003Server 와 동일)


  가. 시작-실행(윈도우키+R) : gpedit.msc

  나. 로컬 컴퓨터 정책→컴퓨터 구성→Windows 설정→보안 설정→로컬 정책→보안 옵션

  다. 대화형 로그온 : [Ctrl+Alt+Del]을 사용할 필요 없음 → 사용

 


2. 시스템 종료 이벤트 추적기 표시 안 함(2003Server와 동일)

  가. 시작-실행(윈도우키+R) : gpedit.msc

  나. 로컬 컴퓨터 정책→컴퓨터 구성→관리 템플릿→시스템

  다. 시스템 종료 이벤트 추적기 표시 → 사용 안 함

 

3. 바탕화면에 Internet Explorer 아이콘 생성

  가. 시작-실행(윈도우키+R) : regedit

  나. HKEY_CURRENT_USER → Software → Microsoft → Windows → CurrentVersion

                                          → Explorer → HideDesktopIcons → NewStartPanel

  다. 새로운 키 값 입력 : "{871C5380-42A0-1069-A2EA-08002B30309D}"=dword:00000000


4. Internet Explorer 보안 설정

  가. 시작-실행(윈도우키+R) : CompMgmtLauncher → 서버 관리자 실행

  나. 서버 요약 → 보안 정보 →IE ESC 구성(보안 강화 구성)

  다. 관리자, 사용자 → 사용 안 함

 

5. 고급 시스템 설정

  가. 시작-실행(윈도우키+R) : SystemPropertiesAdvanced → 고급 시스템 설정 실행

  나. 컴퓨터 이름 : 컴퓨터의 작업 그룹과 네트워크에서 사용할 컴퓨터 이름 변경

  다. 고급 → 성능

    1) 시각 효과 : 최적 설정(모두 끔)에서 필요한 몇 가지만 설정

        가) 메뉴 아래 그림자 표시, 슬라이드 작업 표시줄 단추, 창 및 단추에 시각 스타일 사용,
          
          화면 글꼴의 가장자리 다듬기 등

    2) 고급 : 프로세스 사용 계획 → 다음의 최적 성능을 위해 조정 : 프로그램


    3) 시작 및 복구 : 자동으로 다시 시작 체크 해제, 작은 메모리 덤프
    4) 환경 변수 : 환경 변수가 설정되어 있지 않을 경우 설정

        가) 기본 Path : %SystemRoot%\system32;%SystemRoot%;

 


6. 사운드 활성화

  가. 시작-실행(윈도우키+R) : services.msc

  나. Windows Audio 더블클릭 → 시작유형 → 자동

  다. 제어판 → 소리 → 필요한 옵션 설정
 

7. 테마/Aero 활성화

  가. 시작-실행(윈도우키+R) : services.msc

  나. Themes 더블클릭 → 시작유형 → 자동 (재부팅)

  다. 시작-실행(윈도우키+R) : CompMgmtLauncher → 서버 관리자 실행

  라. 기능 요약 → 기능 추가

                      → 'Quality Windows Audio Video Experience', '데스크톱 경험' 체크 설정후 재부팅

  마. 바탕화면 → 마우스 우클릭 → 개인 설정 → 테마

  바. Windows Vista 또는 찾아보기(C:\Windows\Resources\Themes\Aero.theme)


8. 동시 다운로드 수 설정(다운로드 100개)

  가. 시작-실행(윈도우키+R) : regedit

  나. HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings

  다. 마우스 우클릭 → 새로 만들기(N) → DWORD(32비트) 값(D) : 아래 두 값 추가

    1) MaxConnectionPer1_0Server → 16진수 64 (10진수 100)

    2) MaxConnectionPerServer → 16진수 64 (10진수 100)


 

9. 데이터실행방지(DEP) 기능 해제하기

  가. 내컴퓨터 오른쪽버튼 -> 등록정보

  나. 고급시스템 설정 클릭 

  다. 고급탭 -> 성능에서 설정 버튼 클릭

  라. 데이터 실행방지(DEP)탭에서 [데이터 실행방지(DEP)를 필수 Windows 프로그램 및 서비스에서만 사용(T)] 선택


10. 패스워드 입력하지 않고 윈도 로그온
 
  실행창에 control userpasswords2 입력

  '사용자' 사용자의 이름과 암호를 입력해야 이컴퓨터를 사용할수 있음- 체크해제
 
  체크해제 하기 전에 네모칸 안에 Administrator를 더블클릭 하면 이름을 바꾸기 가능 .
 
 체크해제 후 OK.
 
  패스워드 입력란에 처음 로그인 했을시 입력했던 패스워드를 입력.
 
  밑 칸 한번더 입력.
 
 OK.    재시작...

 

11.설치시 암호설정

  영문 대문자와 소문자 혼용하고 숫자 몇개 넣어서 11자 만드니까 설정되더군요.
 
 그리고 집에서 혼자쓰는 거고 노트북과의 공유 설정을 위해서 암호를 사용해야 하기때문에

  복잡성은 해제하고 영소문자와 숫자 몇개 섞어서 일곱자리로 고쳐서 사용하고 있습니다.


  해제하는 밥업은 명령프롬프트에서

  gpedit.msc>컴퓨터 구성>윈도우즈 설정>보안설정>계정정책>암호정책

  으로 들어가시면 변경가능합니다.


 12. xp flp 사용시 로그인,오프의 번거로움 제거하는 팁 :

  실행창에서 control password2를 입력해도 무반응일 경우
 
  제어판 user account 에 가서 administrator계정에서 change password 클릭한다,

  구암호,신암호 여러칸이 나오는데

  구암호는 flp 처음 설치할때 만들었던 암호 써넣고 신암호 두칸(확인포함)은 비워둔채

  엔터치면 된다.

  

 


 

'IT' 카테고리의 다른 글

이 세상에 간단한 프로젝트는 없다.  (0) 2010.02.26