C# 3.0 개요

 

2007년 3월

앤더스 헬스버그, 매드 토거슨

적용 대상:
   Visual C# 3.0

요약: C# 3.0("C# Orcas")의 기술 개요는 C# 2.0을 기반으로 하는 여러 언어 확장을 도입하여 더 높은 순서의 기능 스타일 클래스 라이브러리를 만들고 사용할 수 있도록 지원합니다. (38페이지 인쇄)

콘텐츠

소개
26.1 암시적으로 형식화된 지역 변수
26.2 확장 메서드
   26.2.1 확장 메서드 선언
   26.2.2 사용 가능한 확장 메서드
   26.2.3 확장 메서드 호출
26.3 람다 식
   26.3.1 익명 메서드 및 람다 식 변환
   26.3.2 대리자 생성 식
   26.3.3 형식 유추
      26.3.3.1 첫 번째 단계
      26.3.3.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 연속이 있는 groupby 절 선택 및 그룹화
      26.7.1.2 명시적 범위 변수 형식
      26.7.1.3 쿼리 식 퇴화
      26.7.1.4 From, let, where, join 및 orderby 절
      26.7.1.5 절 선택
      26.7.1.6 Groupby 절
      26.7.1.7 투명한 식별자
   26.7.2 쿼리 식 패턴
26.8 식 트리
   26.8.1 오버로드 확인
26.9 자동으로 구현된 속성

소개

이 문서에는 Visual C# 3.0에 적용되는 몇 가지 업데이트가 포함되어 있습니다. 포괄적인 사양은 언어 릴리스와 함께 제공됩니다.

C# 3.0("C# Orcas")에는 더 높은 순서의 기능 스타일 클래스 라이브러리를 만들고 사용할 수 있도록 C# 2.0을 기반으로 하는 여러 언어 확장이 도입되었습니다. 확장을 사용하면 관계형 데이터베이스 및 XML과 같은 도메인에서 쿼리 언어의 표현력이 동일한 컴퍼지션 API를 생성할 수 있습니다. 확장에는 다음이 포함됩니다.

  • 지역 변수를 초기화하는 데 사용되는 식에서 지역 변수의 형식을 유추할 수 있도록 암시적으로 형식화된 지역 변수입니다.
  • 확장 메서드를 사용하면 기존 형식 및 생성된 형식을 추가 메서드로 확장할 수 있습니다.
  • 람다 식은 대리자 형식과 식 트리 모두에 대한 향상된 형식 유추 및 변환을 제공하는 익명 메서드의 진화입니다.
  • 개체의 생성 및 초기화를 용이하게 하는 개체 이니셜라이저입니다.
  • 튜플 형식인 익명 형식은 개체 이니셜라이저에서 자동으로 유추되고 생성됩니다.
  • 배열 이니셜라이저에서 배열의 요소 형식을 유추하는 배열 만들기 및 초기화 형식인 암시적으로 형식화된 배열입니다.
  • 쿼리 식 - SQL 및 XQuery와 같은 관계형 및 계층적 쿼리 언어와 유사한 쿼리에 대한 언어 통합 구문을 제공합니다.
  • 람다 식을 코드(대리자) 대신 데이터(식 트리)로 나타낼 수 있도록 허용하는 식 트리입니다.

이 문서는 이러한 기능에 대한 기술 개요입니다. 이 문서에서는 C# 언어 사양 버전 1.2 (§1~§18) 및 C# 언어 사양 버전 2.0 (§19~§25)을 참조하며, 둘 다 C# 언어 홈페이지(https://msdn.microsoft.com/vcsharp/aa336809.aspx)에서 사용할 수 있습니다.

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

암시적으로 형식화된 지역 변수 선언에서 선언되는 지역 변수의 형식은 변수를 초기화하는 데 사용되는 식에서 유추됩니다. 지역 변수 선언이 var을 형식으로 지정하고 var이라는 형식이 scope 없는 경우 선언은 암시적으로 형식화된 지역 변수 선언입니다. 예:

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;               // Error, no initializer to infer type from
var y = {1, 2, 3};   // Error, collection initializer not permitted
var z = null;        // Error, null type not permitted
var u = x => x + 1;  // Error, lambda expressions do not have a type
var v = v++;         // Error, initializer cannot refer to variable itself

이전 버전과의 호환성을 위해 지역 변수 선언이 var을 형식으로 지정하고 var이라는 형식이 scope 경우 선언은 해당 형식을 참조합니다. var이라는 형식이 대문자로 된 시작 형식 이름의 설정된 규칙을 위반하므로 이 상황은 발생하지 않을 수 있습니다.

for 문의 for-initializer(§8.8.3) 및 using 문(§8.13)의 리소스 획득은 암시적으로 형식화된 지역 변수 선언일 수 있습니다. 마찬가지로 foreach 문(§8.8.4)의 반복 변수를 암시적으로 형식화된 지역 변수로 선언할 수 있습니다. 이 경우 반복 변수의 형식이 열거되는 컬렉션의 요소 형식으로 유추됩니다. 예제에서

int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);

n의 형식은 숫자의 요소 형식인 int로 유추됩니다.

local-variable-declaration, for-initializer, resource-acquisitionforeach-statement만 암시적으로 형식화된 지역 변수 선언을 포함할 수 있습니다.

26.2 확장 메서드

확장 메서드는 instance 메서드 구문을 사용하여 호출할 수 있는 정적 메서드입니다. 결과적으로 확장 메서드는 추가 메서드를 사용하여 기존 형식과 생성된 형식을 확장할 수 있도록 합니다.

참고 확장 메서드는 검색할 수 없으며 instance 메서드보다 기능이 더 제한적입니다. 이러한 이유로 확장 메서드는 instance 메서드가 가능하거나 가능하지 않은 경우에만 드물게 사용하는 것이 좋습니다. 속성, 이벤트 및 연산자와 같은 다른 종류의 확장 멤버가 고려 중이지만 현재 지원되지 않습니다.

26.2.1 확장 메서드 선언

확장 메서드는 메서드의 첫 번째 매개 변수에서 키워드(keyword) 한정자로 지정하여 선언됩니다. 확장 메서드는 제네릭이 아닌 중첩되지 않은 정적 클래스에서만 선언할 수 있습니다. 다음은 두 개의 확장 메서드를 선언하는 정적 클래스의 예입니다.

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;
      }
   }
}

확장 메서드의 첫 번째 매개 변수에는 이외의 한정자가 있을 수 없으며 매개 변수 형식은 포인터 형식일 수 없습니다.

확장 메서드에는 일반 정적 메서드의 모든 기능이 있습니다. 또한 가져온 후에는 instance 메서드 구문을 사용하여 확장 메서드를 호출할 수 있습니다.

26.2.2 사용 가능한 확장 메서드

확장 메서드는 정적 클래스에서 선언되거나 해당 네임스페이스의 using-namespace-directives(§9.3.2)를 통해 가져온 경우 네임스페이스에서 사용할 수 있습니다. 가져온 네임스페이스에 포함된 형식을 가져오는 것 외에도 using-namespace-directive 는 가져온 네임스페이스의 모든 정적 클래스에서 모든 확장 메서드를 가져옵니다.

실제로 사용 가능한 확장 메서드는 첫 번째 매개 변수에 의해 지정된 형식에 대한 추가 메서드로 표시되며 일반 instance 메서드보다 우선 순위가 낮습니다. 예를 들어 위의 예제에서 Acme.Utilities 네임스페이스를 using-namespace-directive로 가져오는 경우

using Acme.Utilities;

instance 메서드 구문을 사용하여 정적 클래스 Extensions에서 확장 메서드를 호출할 수 있습니다.

string s = "1234";
int i = s.ToInt32();               // Same as Extensions.ToInt32(s)
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] a = digits.Slice(4, 3);      // Same as Extensions.Slice(digits, 4, 3)

26.2.3 확장 메서드 호출

확장 메서드 호출에 대한 자세한 규칙은 다음에 설명되어 있습니다. 양식 중 하나의 메서드 호출(§7.5.5.1)에서

expr . identifier ( )
expr . identifier ( args )
expr . identifier < typeargs > ( )
expr . identifier < typeargs > ( args )

호출의 정상적인 처리에서 적용 가능한 instance 메서드를 찾지 못하면(특히 호출에 대한 후보 메서드 집합이 비어 있는 경우) 구문을 확장 메서드 호출로 처리하려고 시도합니다. 메서드 호출은 먼저 각각 다음 중 하나로 다시 작성됩니다.

identifier ( expr )
identifier ( expr , args )
identifier < typeargs > ( expr )
identifier < typeargs > ( expr , args )

다시 작성된 양식은 식별자가 확인되는 방식을 제외하고 정적 메서드 호출로 처리됩니다. 가장 가까운 네임스페이스 선언부터 시작하여 각 바깥쪽 네임스페이스 선언을 계속 진행하며, 포함된 컴파일 단위로 끝나는 경우, 식별자가 지정한 이름으로 네임스페이스의 모든 사용 가능하고 액세스 가능한 확장 메서드로 구성된 메서드 그룹으로 다시 작성된 메서드 호출을 처리하려고 연속해서 시도합니다.. 이 집합에서 적용할 수 없는 메서드(§7.4.2.1)와 암시적 ID, 참조 또는 boxing 변환이 없는 메서드를 첫 번째 인수에서 첫 번째 매개 변수로 제거합니다. 비어 있지 않은 후보 메서드 집합을 생성하는 첫 번째 메서드 그룹은 재작성된 메서드 호출에 대해 선택된 메서드 그룹이며, 일반 오버로드 확인(§7.4.2)이 적용되어 후보 집합에서 최상의 확장 메서드를 선택합니다. 모든 시도가 빈 후보 메서드 집합을 생성하면 컴파일 시간 오류가 발생합니다.

위의 규칙은 instance 메서드가 확장 메서드보다 우선하고 내부 네임스페이스 선언에서 사용할 수 있는 확장 메서드가 외부 네임스페이스 선언에서 사용할 수 있는 확장 메서드보다 우선한다는 것을 의미합니다. 예:

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 람다 식

C# 2.0에는 대리자 값이 필요한 "인라인"으로 코드 블록을 작성할 수 있는 익명 메서드가 도입되었습니다. 무명 메서드는 기능 프로그래밍 언어의 표현력을 대부분 제공하지만 익명 메서드 구문은 본질적으로 다소 세부적이고 명령적입니다. 람다 식은 익명 메서드를 작성하기 위한 보다 간결하고 기능적인 구문을 제공합니다.

람다 식은 매개 변수 목록으로 작성되고 그 뒤에 => 토큰, 식 또는 문 블록이 잇습니다.

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

=> 연산자는 대입(=)과 우선 순위가 동일하며 오른쪽 연관성이 있습니다.

람다 식의 매개 변수는 명시적으로 또는 암시적으로 입력할 수 있습니다. 명시적으로 형식화된 매개 변수 목록에서 각 매개 변수의 형식이 명시적으로 명시되어 있습니다. 암시적으로 형식화된 매개 변수 목록에서 매개 변수 형식은 람다 식이 발생하는 컨텍스트에서 유추됩니다. 특히 람다 식이 호환되는 대리자 형식으로 변환될 때 해당 대리자 형식은 매개 변수 형식(§26.3.1)을 제공합니다.

암시적으로 형식화된 단일 매개 변수가 있는 람다 식에서는 매개 변수 목록에서 괄호를 생략할 수 있습니다. 즉, 폼의 람다 식입니다.

( param ) => expr

을(를) 축약할 수 있습니다.

param => expr

람다 식의 몇 가지 예는 다음과 같습니다.

x => x + 1                     // Implicitly typed, expression body
x => { return x + 1; }         // Implicitly typed, statement body
(int x) => x + 1               // Explicitly typed, expression body
(int x) => { return x + 1; }   // Explicitly typed, statement body
(x, y) => x * y               // Multiple parameters
() => Console.WriteLine()      // No parameters

일반적으로 C# 2.0 사양의 §21에 제공된 익명 메서드의 사양은 람다 식에도 적용됩니다. 람다 식은 다음 점을 제외하고 무명 메서드와 기능적으로 유사합니다.

  • 익명 메서드를 사용하면 매개 변수 목록을 완전히 생략하여 매개 변수 목록의 형식을 위임할 수 있습니다.
  • 람다 식을 사용하면 매개 변수 형식을 생략하고 유추할 수 있지만 익명 메서드는 매개 변수 형식을 명시적으로 명시해야 합니다.
  • 람다 식의 본문은 식 또는 문 블록일 수 있지만 무명 메서드의 본문은 문 블록일 수 있습니다.
  • 식 본문이 있는 람다 식은 식 트리(§26.8)로 변환할 수 있습니다.

26.3.1 익명 메서드 및 람다 식 변환

참고 이 섹션은 §21.3을 대체합니다.

anonymous-method-expression람다 식은 특수 변환 규칙이 있는 값으로 분류됩니다. 값에는 형식이 없지만 호환되는 대리자 형식으로 암시적으로 변환할 수 있습니다. 특히 대리자 형식 D 는 제공된 익명 메서드 또는 람다 식 L 과 호환됩니다.

  • DL 의 매개 변수 수는 동일합니다.
  • Lanonymous-method-signature을 포함하지 않는 익명 메서드인 경우 D의 매개 변수에 매개 변수 한정자가 없는 한 Dout 에는 모든 형식의 매개 변수가 0개 이상 있을 수 있습니다.
  • L에 명시적으로 형식화된 매개 변수 목록이 있는 경우 D의 각 매개 변수에는 L의 해당 매개 변수와 동일한 형식 및 한정자가 있습니다.
  • L이 암시적으로 형식화된 매개 변수 목록이 있는 람다 식인 경우 D에는 ref 또는 out 매개 변수가 없습니다.
  • Dvoid 반환 형식이 있고 L 본문이 식인 경우 L의 각 매개 변수에 D의 해당 매개 변수 형식이 지정되면 L의 본문은 식(§8.6)으로 허용되는 유효한 식(wrt §7)입니다.
  • Dvoid 반환 형식이 있고 L 본문이 문 블록인 경우 L의 각 매개 변수에 D의 해당 매개 변수 형식이 지정되면 L의 본문은 반환 문이 식을 지정하지 않는 유효한 문 블록(wrt §8.2)입니다.
  • D에 void가 아닌 반환 형식이 있고 L 본문이 식인 경우 L의 각 매개 변수에 D의 해당 매개 변수 형식이 지정되면 L 본문은 D의 반환 형식으로 암시적으로 변환할 수 있는 유효한 식(wrt §7)입니다.
  • D에 무효 반환 형식이 있고 L 본문이 문 블록인 경우 L의 각 매개 변수에 D의 해당 매개 변수 형식이 지정되면 L 본문은 각 반환 문이 D의 반환 형식으로 암시적으로 변환할 수 있는 식을 지정하는 연결할 수 없는 끝점이 있는 유효한 문 블록(wrt §8.2)입니다.

다음 예제에서는 A 형식의 인수를 사용하고 R >형식의 값을 반환하는 함수를 나타내는 제네릭 대리자 형식 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;      // Error

각 람다 식의 매개 변수 및 반환 형식은 람다 식이 할당된 변수의 형식에서 결정됩니다. 첫 번째 할당은 x에 int 형식이 제공되면 x + 1이 int >형식으로 암시적으로 변환할 수 있는 유효한 식이므로 람다 식을 대리자 형식 Func<int,int로 변환합니다. 마찬가지로 두 번째 할당은 x + 1(int 형식)의 결과가 암시적으로 double 형식으로 변환될 수 있기 때문에 람다 식을 대리자 형식 Func<int,double>로 변환합니다. 그러나 세 번째 할당은 x가 double 형식으로 제공되면 x + 1(double 형식)의 결과가 int형식으로 암시적으로 변환할 수 없기 때문에 컴파일 시간 오류입니다.

26.3.2 대리자 만들기 식

참고 이 섹션은 §21.10을 대체합니다.

대리자 생성 식(§7.5.10.3)은 인수가 메서드 그룹으로 분류된 식, 익명 메서드 또는 람다 식으로 분류된 식 또는 대리자 형식의 값이 되도록 확장됩니다.

D가 대리자 형식이고 E 식인 새 D(E) 형식의 대리자 생성 식의 컴파일 시간 처리는 다음 단계로 구성됩니다.

  • E가 메서드 그룹인 경우 메서드 그룹 변환(§21.9)이 E에서 D로 존재해야 하며 대리자 만들기 식은 해당 변환과 동일한 방식으로 처리됩니다.
  • E가 무명 메서드 또는 람다 식인 경우 익명 메서드 또는 람다 식 변환(§ 26.3.1)이 E에서 D로 존재해야 하며 대리자 만들기 식은 해당 변환과 동일한 방식으로 처리됩니다.
  • E가 대리자 형식의 값인 경우 E의 메서드 서명은 D와 일치해야 하며(§21.9) 결과는 E와 동일한 호출 목록을 참조하는 D 형식의 새로 만든 대리자를 참조합니다. ED와 일치하지 않으면 컴파일 시간 오류가 발생합니다.

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);               // Calls Choose<int>
string s = Chooser.Choose("foo", "bar");      // Calls Choose<string>

형식 유추를 통해 intstring 형식 인수는 인수에서 메서드로 결정됩니다.

형식 유추는 메서드 호출(§20.9.7)의 컴파일 시간 처리의 일부로 발생하며 호출의 오버로드 확인 단계 전에 발생합니다. 메서드 호출에서 특정 메서드 그룹을 지정하고 메서드 호출의 일부로 형식 인수를 지정하지 않으면 메서드 그룹의 각 제네릭 메서드에 형식 유추가 적용됩니다. 형식 유추에 성공하면 유추된 형식 인수를 사용하여 후속 오버로드 확인을 위한 인수 형식을 결정합니다. 오버로드 확인에서 제네릭 메서드를 호출할 메서드로 선택하면 유추된 형식 인수가 호출의 실제 형식 인수로 사용됩니다. 특정 메서드에 대한 형식 유추가 실패하면 해당 메서드는 오버로드 확인에 참여하지 않습니다. 형식 유추 자체가 실패해도 컴파일 시간 오류가 발생하지 않습니다. 그러나 오버로드 확인이 적용 가능한 메서드를 찾지 못하면 컴파일 시간 오류가 발생하는 경우가 많습니다.

제공된 인수 수가 메서드의 매개 변수 수와 다른 경우 유추가 즉시 실패합니다. 그렇지 않으면 제네릭 메서드에 다음 서명이 있다고 가정합니다.

Tr M<X1...Xn>(T1 x1 ... Tm xm)

M(e1... 형식의 메서드 호출 사용 em) 형식 유추의 작업은 고유한 형식 인수 S1...을 찾는 것입니다. 각 형식 매개 변수 X1에 대한 Sn... M<S1을 호출하도록 Xn... Sn>(e1... em)이 유효해집니다.

유추 과정에서 각 형식 매개 변수 Xi는 특정 형식 Si고정되거나 연결된 경계 집합과 고정되지 않습니다. 각 경계는 일부 형식 T입니다. 처음에 각 형식 변수 Xi는 빈 경계 집합으로 고정되지 않습니다.

형식 유추는 단계적으로 발생합니다. 각 단계에서는 이전 단계의 결과에 따라 더 많은 형식 변수에 대한 형식 인수를 유추하려고 합니다. 첫 번째 단계에서는 몇 가지 초기 경계 유추를 만드는 반면, 두 번째 단계에서는 형식 변수를 특정 형식으로 수정하고 추가 경계를 유추합니다. 두 번째 단계는 여러 번 반복해야 할 수 있습니다.

참고 다음 전체에서 대리자 형식을 참조하는 경우 D가 대리자 형식인 식<D> 형식도 포함하도록 해야 합니다. 식<D의 인수 및 반환 형식은 D>의 인수입니다.

참고 형식 유추는 제네릭 메서드가 호출되는 경우에만 발생합니다. 메서드 그룹의 변환에 대한 형식 유추는 §26.3.3.12에서 설명하고 식 집합의 가장 일반적인 형식을 찾는 방법은 §26.3.3.13에 설명되어 있습니다.

26.3.3.1 첫 번째 단계

각 메서드 인수 에 대해 ei:

  • 명시적 인수 형식 유추(§26.3.3.7)는 ei 가 람다 식, 익명 메서드 또는 메서드 그룹인 경우 Ti 형식의 ei 로 만들어집니다.
  • 출력 형식 유추(§26.3.3.6)는 ei 가 람다 식, 익명 메서드 또는 메서드 그룹이 아닌 경우 Ti 형식의 ei 로 만들어집니다.

26.3.3.2 두 번째 단계

(§26.3.3.5)에 의존하는 모든 고정되지 않은 형식 변수 Xi고정되지 않습니다(§26.3.3.10).

이러한 형식 변수가 없으면 모든 고정되지 않은 형식 변수 Xi 가 수정되어 다음 모든 변수가 유지됩니다.

  • Xi에 따라 하나 이상의 형식 변수 **Xj **가 있습니다.
  • Xi 에는 비어 있지 않은 경계 집합이 있습니다.

이러한 형식 변수가 없고 아직 고정되지 않은 형식 변수가 있는 경우 형식 유추가 실패합니다. 더 이상 고정되지 않은 형식 변수가 없으면 형식 유추가 성공합니다. 그렇지 않으면 출력 형식(§26.3.3.4)에 고정되지 않은 형식 변수 Xj가 포함되지만 입력 형식(§26.3.3.3)이 포함되지 않는 해당 인수 형식 Ti를 사용하는 모든 인수의 경우 출력 형식 유추(§26.3.3.6)는 Ti 형식의 ei에 대해 만들어집니다. 그런 다음 두 번째 단계가 반복됩니다.

26.3.3.3 입력 형식

e가 메서드 그룹이거나 암시적으로 형식화된 람다 식이고 T가 대리자 형식인 경우 T 의 모든 인수 형식은 형식이 Te입력 형식입니다.

26.3.3.4 출력 형식

e가 메서드 그룹, 무명 메서드, 문 람다 또는 식 람다이고 T가 대리자 형식인 경우 T의 반환 형식은 T형식이 있는 e출력 형식입니다.

26.3.3.5 의존성

형식이 Tk Xj인 일부 인수 ek가 Tk 형식의 입력 형식에서 발생하고 XiTk 형식의 출력 형식에서 발생하는 경우 고정되지 않은 형식 변수 Xi는 고정되지 않은 형식 변수 Xj직접 의존합니다.

XjXjXi에 직접 의존하거나 Xi가 Xk에 직접 의존하고 XkXj에 의존하는 경우 Xi에 의존합니다. 따라서 "depends on"은 "에 직접 의존"의 전이적이지만 반사적인 폐쇄는 아닙니다.

26.3.3.6 출력 형식 유추

출력 형식 유추는 다음과 같은 방식으로 T 형식의 e 식에서 만들어집니다.

  • e가 유추된 반환 형식 U(§26.3.3.11)가 있는 람다 또는 익명 메서드이고 T가 반환 형식 Tb인 대리자 형식인 경우 TbU에서 하한 유추(§26.3.3.9)가 만들어집니다.
  • 그렇지 않으면 e가 메서드 그룹이고 T가 매개 변수 형식이 T1인 대리자 형식인 경우... T1 형식을 사용하는 e의 Tk 및 오버로드 확인... Tk는 반환 형식이 U인 단일 메서드를 생성한 다음, U에서 Tb에 대해 하 한 유추를 만듭니다.
  • 그렇지 않으면 eU 형식의 식인 경우 TU에서 하한 유추가 만들어집니다.
  • 그렇지 않으면 유추가 이루어지지 않습니다.

26.3.3.7 명시적 인수 형식 유추

명시적 인수 형식 유추는 다음과 같은 방식으로 T 형식의 e 식에서 만들어집니다.

  • e가 명시적으로 형식화된 람다 식이거나 인수 형식이 U1인 anonymous 메서드인 경우... UkT는 매개 변수 형식이 V1인 대리자 형식입니다. 그러면 각Ui에 대해 정확한 유추(§26.3.3.8)가 해당 Vi.Ui에서 만들어집니다.

26.3.3.8 정확한 유추

형식 V에 대한 형식 U의 정확한 유추는 다음과 같이 수행됩니다.

  • V가 고정되지 않은 Xi 중 하나인 경우 UXi의 경계 집합에 추가됩니다.
  • 그렇지 않으면 UUe[...] 배열 형식이고 V 가 동일한 순위의 배열 형식 Ve[...] 인 경우 Ue 에서 Ve 로의 정확한 유추가 이루어집니다.
  • 그렇지 않으면 V 가 생성된 형식 C<V1인 경우... Vk>U생성된 형식 C<U1... 그런 다음 영국은>Ui 에서 해당 Vi로 정확한 유추가 이루어집니다.
  • 그렇지 않으면 유추가 이루어지지 않습니다.

26.3.3.9 하한 유추

형식 V에 대한 U 형식의 하한 유추는 다음과 같이 수행됩니다.

  • V가 고정되지 않은 Xi 중 하나인 경우 UXi의 경계 집합에 추가됩니다.
  • 그렇지 않으면 UUe[...] 배열 형식이고 V가 동일한 순위의 배열 형식 Ve[...]이거나 U가 1차원 배열 형식 **Ue[]**이고 VIEnumerable<Ve>, ICollection<Ve> 또는 IList<Ve 중 하나인 경우 다음을 수행>합니다.
    • Ue가 참조 형식으로 알려진 경우 Ue에서 Ve로의 하한 유추가 만들어집니다.
    • 그렇지 않으면 Ue 에서 Ve 로의 정확한 유추가 이루어집니다.
  • 그렇지 않으면 V가 생성된 형식 C<V1인 경우... Vk> 및 U1 형식의 고유한 집합이 있습니다... 표준 암시적 변환이 U에서 C<U1로 존재하도록 하는 영국... 그런 다음 영국은> 해당 Vi에 대한 각 Ui에서 정확한 유추가 이루어집니다.
  • 그렇지 않으면 유추가 이루어지지 않습니다.

26.3.3.10 수정

범위 집합이 있는 고정되지 않은 형식 변수 Xi 는 다음과 같이 고정 됩니다.

  • Uj 후보 유형 집합은 Xi의 경계 집합에 있는 모든 형식의 집합으로 시작됩니다.
  • 그런 다음 , Xi 에 대한 각 바인딩을 차례로 검사합니다. X의 각 바인딩된 U에 대해 U에서 표준 암시적 변환이 없는모든 형식 Uj가 후보 집합에서 제거됩니다.
  • 나머지 후보 유형 중 Uj 에 다른 모든 후보 형식으로 표준 암시적 변환이 있는 고유한 형식 V 가 있는 경우 XiV로 고정됩니다.
  • 그렇지 않으면 형식 유추가 실패합니다.

26.3.3.11 유추된 반환 형식

형식 유추 및 오버로드 확인을 위해 람다 식 or anonymous 메서드 e유추된 반환 형식은 다음과 같이 결정됩니다.

  • e의 본문이 식인 경우 해당 식의 형식은 e의 유추된 반환 형식입니다.
  • e 본문이 문 블록인 경우 블록의 반환 문에 있는 식 집합에 가장 일반적인 형식이 있고 해당 형식이 null 형식이 아닌 경우 해당 형식은 유추된 L 반환 형식입니다.
  • 그렇지 않으면 L에 대해 반환 형식을 유추할 수 없습니다.

람다 식과 관련된 형식 유추의 예로 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 네임스페이스를 가져오고 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 인수는 원본 매개 변수와 관련이 있으며 TCustomer로 유추합니다. 그런 다음 위에서 c 설명한 람다 식 형식 유추 프로세스를 사용하여 Customer 형식을 지정하고 c.Name 식은 문자열로 유추하는 S선택기 매개 변수의 반환 형식과 관련이 있습니다. 따라서 호출은 다음과 같습니다.

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

결과는 IEnumerable<문자열> 형식입니다.

다음 예제에서는 람다 식 형식 유추에서 형식 정보를 제네릭 메서드 호출의 인수 간에 "흐름"으로 허용하는 방법을 보여 줍니다. 지정된 메서드:

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" 인수는 매개 변수와 관련이 있으며 X문자열로 유추합니다. 그런 다음 첫 번째 람다 식 매개 변수 s에 유추된 형식 string이 지정되고 TimeSpan.Parse 식은 f1의 반환 형식과 관련이 있으며 YSystem.TimeSpan으로 유추합니다. 마지막으로 두 번째 람다 식 t의 매개 변수에는 유추된 형식 System.TimeSpan이 지정되고 t.TotalSeconds 식은 f2의 반환 형식과 관련이 있으며 Zdouble로 유추합니다. 따라서 호출 결과는 double 형식입니다.

26.3.3.12 메서드 그룹 변환에 대한 형식 유추

제네릭 메서드의 호출과 마찬가지로 제네릭 메서드를 포함하는 메서드 그룹 M 이 지정된 대리자 형식 D에 할당될 때도 형식 유추를 적용해야 합니다. 지정된 메서드

Tr M<X1...Xn>(T1 x1 ... Tm xm)

그리고 형식 유추의 태스크는 형식 인수 S1을 찾는 것입니다 대리자 형식 D에 할당되는 메서드 그룹 M... 식이 되도록 Sn:

M<S1...Sn>

D에 할당할 수 있게 됩니다.

제네릭 메서드 호출에 대한 형식 유추 알고리즘과 달리 이 경우 인수 형식만 있고 인수 식은 없습니다. 특히 람다 식은 없으므로 여러 단계의 유추가 필요하지 않습니다.

대신, 모든 Xi는 고정되지 않은 것으로 간주되며, D의 각 인수 형식 Uj에서 M의 해당 매개 변수 형식 Tj로 하한 유추가 이루어집니다.Xi에 대한 경계가 없으면 형식 유추가 실패합니다. 그렇지 않으면 모든 Xi 가 형식 유추의 결과인 해당 Si로 고정됩니다.

26.3.3.13 식 집합의 가장 일반적인 형식 찾기

경우에 따라 식 집합에 대해 공통 형식을 유추해야 합니다. 특히 암시적으로 형식화된 배열의 요소 형식과 익명 메서드 및 문 람다의 반환 형식은 이러한 방식으로 찾을 수 있습니다.

직관적으로, e1 식 집합을 제공 ... em 이 유추는 메서드를 호출하는 것과 동일해야 합니다.

Tr M<X>(X x1 ... X xm)

ei를 인수로 사용합니다.

더 정확하게 말하면 유추는 고정되지 않은 형식 변수 X로 시작됩니다. 출력 형식 유추는 X 형식의 각 ei에서 만들어집니다. 마지막으로 X는 고정되고 결과 형식 S 은 식에 대한 결과 공통 형식입니다.

26.3.4 오버로드 확인

인수 목록의 람다 식은 특정 상황에서 오버로드 확인에 영향을 줍니다. 정확한 규칙은 §7.4.2.3을 참조하세요.

다음 예제에서는 람다가 오버로드 해상도에 미치는 영향을 보여 줍니다.

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> 클래스에는 두 Sum 가지 메서드가 있습니다. 각각은 목록 항목에서 합계를 계산할 값을 추출하는 선택기 인수를 사용합니다. 추출된 값은 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);
   ...
}

orderDetails.Sum의 첫 번째 호출에서 두 Sum 메서드는 모두 적용할 수 있습니다. 람다 식 d => d.UnitCountFunc Detail,int> 및 Func<<Detail,double> 모두와 호환되기 때문입니다. 그러나 오버로드 해상도는 Func Detail로의 변환이 Func<Detail,double>>로 변환하는 것보다 더 낫기 때문에 첫 번째 Sum 메서드를< 선택합니다.

orderDetails.Sum의 두 번째 호출에서 두 번째 Sum 메서드만 적용할 수 있습니다. 람다 식 d => d.UnitPrice * d.UnitCountdouble 형식의 값을 생성하기 때문입니다. 따라서 오버로드 확인은 해당 호출에 대한 두 번째 Sum 메서드를 선택합니다.

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

개체 만들기 식(§7.5.10.1)에는 새로 만든 개체의 멤버 또는 새로 만든 컬렉션의 요소를 초기화하는 개체 또는 컬렉션 이니셜라이저가 포함될 수 있습니다.

object-creation-expression:
new   type   (   argument-listopt)   object-or-collection-initializeroptnew   type   object-or-collection-initializer
object-or-collection-initializer:
object-initializer
collection-initializer

개체 만들기 식은 개체 또는 컬렉션 이니셜라이저를 포함하는 경우 생성자 인수 목록과 괄호를 묶는 것을 생략할 수 있습니다. 생성자 인수 목록을 생략하고 괄호를 묶는 것은 빈 인수 목록을 지정하는 것과 같습니다.

개체 또는 컬렉션 이니셜라이저를 포함하는 개체 만들기 식의 실행은 먼저 instance 생성자를 호출한 다음 개체 또는 컬렉션 이니셜라이저에서 지정한 멤버 또는 요소 초기화를 수행하는 것으로 구성됩니다.

개체 또는 컬렉션 이니셜라이저가 초기화되는 개체 instance 참조할 수 없습니다.

개체 및 컬렉션 이니셜라이저를 제네릭으로 올바르게 구문 분석하려면 §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)과 동일한 방식으로 처리됩니다.

등호 다음에 개체 이니셜라이저를 지정하는 멤버 이니셜라이저는 중첩된 개체 이니셜라이저(예: 포함된 개체의 n 초기화)입니다. 필드 또는 속성에 새 값을 할당하는 대신 중첩된 개체 이니셜라이저의 할당은 필드 또는 속성의 멤버에 대한 할당으로 처리됩니다. 중첩된 개체 이니셜라이저는 값 형식의 속성이나 값 형식이 있는 읽기 전용 필드에 적용할 수 없습니다.

등호 다음에 컬렉션 이니셜라이저를 지정하는 멤버 이니셜라이저는 포함된 컬렉션의 초기화입니다. 필드 또는 속성에 새 컬렉션을 할당하는 대신 이니셜라이저에 지정된 요소가 필드 또는 속성에서 참조하는 컬렉션에 추가됩니다. 필드 또는 속성은 §26.4.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; } }
}

의 instance Point 다음과 같이 만들고 초기화할 수 있습니다.

var a = new Point { X = 0, Y = 1 };

와 같은 효과를 내는

var __a = new Point();
__a.X = 0;
__a.Y = 1; 
var a = __a;

여기서 __a 보이지 않으며 액세스할 수 없는 임시 변수입니다. 다음 클래스는 두 지점에서 만든 사각형을 나타냅니다.

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 instance 만들고 초기화할 수 있습니다.

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 생성자가 포함된 두 개의 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   }

컬렉션 이니셜라이저는 {} 토큰으로 묶고 쉼표로 구분된 요소 이니셜라이저 시퀀스로 구성됩니다. 각 요소 이니셜라이저는 초기화할 컬렉션 개체에 추가할 요소를 지정하고 {} 토큰으로 묶고 쉼표로 구분된 식 목록으로 구성됩니다. 단일 식 요소 이니셜라이저는 중괄호 없이 작성할 수 있지만 멤버 이니셜라이저와의 모호성을 방지하기 위해 대입 식이 될 수는 없습니다. 할당 식이 아닌 프로덕션은 §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; } }
}

목록<연락처>는 다음과 같이 만들고 초기화할 수 있습니다.

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을 사용하면 익명 개체 이니셜라이저와 함께 연산자를 사용하여 익명 형식의 개체를 만들 수 있습니다.

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

익명 개체 이니셜라이저는 익명 형식을 선언하고 해당 형식의 instance 반환합니다. 익명 형식은 에서 object직접 상속하는 이름 없는 클래스 형식입니다. 익명 형식의 멤버는 형식의 인스턴스를 만드는 데 사용되는 개체 이니셜라이저에서 유추된 읽기/쓰기 속성의 시퀀스입니다. 특히 양식의 익명 개체 이니셜라이저

new { p1 = e1 , p2 = e2 , ...pn = en }

는 익명 형식의 폼을 선언합니다.

class __Anonymous1
{
   private T1f1 ;
   private T2f2 ;
   ...
   private Tnfn ;
   public T1p1 { get { return f1 ; } set { f1 = value ; } }
   public T2p2 { get { return f2 ; } set { f2 = value ; } }
   ...
   public T1p1 { get { return f1 ; } set { f1 = value ; } }
}

여기서 각 Tx해당 식의 형식입니다. 익명 개체 이니셜라이저의 식이 null 형식이거나 안전하지 않은 형식인 경우 컴파일 시간 오류입니다.

익명 형식의 이름은 컴파일러에서 자동으로 생성되며 프로그램 텍스트에서 참조할 수 없습니다.

동일한 프로그램 내에서 동일한 이름의 속성 시퀀스와 동일한 순서로 컴파일 시간 형식의 시퀀스를 지정하는 두 개의 익명 개체 이니셜라이저는 동일한 익명 형식의 인스턴스를 생성합니다. (이 정의는 관찰 가능하고 리플렉션과 같은 특정 상황에서 재질이기 때문에 속성의 순서를 포함합니다.)

예제에서

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

p1p2가 동일한 익명 형식이므로 마지막 줄의 할당이 허용됩니다.

익명 형식 의 EqualsGetHashcode 메서드는 속성의 EqualsGetHashcode 측면에서 정의되므로 동일한 익명 형식의 두 인스턴스는 모든 속성이 같은 경우에만 동일합니다.

멤버 선언자는 간단한 이름(§7.5.2) 또는 멤버 액세스(§7.5.4)로 축약할 수 있습니다. 이를 프로젝션 이니셜라이저 라고 하며 의 선언에 대한 약식이며 이름이 같은 속성에 할당됩니다. 특히 양식의 멤버 선언자

identifier                        expr . identifier

은 각각 다음과 정확히 동일합니다.

identifer = identifieridentifier = expr . identifier

따라서 프로젝션 이니셜라이저에서 식별자는 값과 값이 할당된 필드 또는 속성을 모두 선택합니다. 직관적으로 프로젝션 이니셜라이저는 값뿐만 아니라 값 이름도 프로젝션합니다.

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

배열 만들기 식 구문(§7.5.10.2)은 암시적으로 형식화된 배열 만들기 식을 지원하도록 확장됩니다.

array-creation-expression:
...
new[]   array-initializer

암시적으로 형식화된 배열 만들기 식에서 배열 instance 형식은 배열 이니셜라이저에 지정된 요소에서 유추됩니다. 특히 배열 이니셜라이저의 식 형식에 의해 형성된 집합에는 집합의 각 형식을 암시적으로 변환할 수 있는 정확히 하나의 형식이 포함되어야 하며, 해당 형식이 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" };         // Error

마지막 식은 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

쿼리 식은 §26.3에서 발생하는 정의인 할당 식이 아닌 식으로 분류됩니다.

쿼리 식은 from 절로 시작하고 select 또는 group 절로 끝납니다. 절 초기 뒤에는 0 개 이상,let, where 또는 join 절이 뒤따를 수 있습니다. 각 from 절은 시퀀스에 걸쳐 범위 변수를 도입하는 생성기입니다. 각 let 절은 값을 계산하고 해당 값을 나타내는 식별자를 도입합니다. 각 where 절은 결과에서 항목을 제외하는 필터입니다. 각 조인 절은 원본 시퀀스의 지정된 키를 다른 시퀀스의 키와 비교하여 일치하는 쌍을 생성합니다. 각 orderby 절은 지정된 조건에 따라 항목을 다시 정렬합니다. 최종 select 또는 group 절은 범위 변수를 기준으로 결과의 모양을 지정합니다. 마지막으로, into 절을 사용하여 한 쿼리의 결과를 후속 쿼리에서 생성기로 처리하여 쿼리를 "스플라이스"할 수 있습니다.

쿼리 식의 모호성

쿼리 식에는 지정된 컨텍스트에서 특별한 의미가 있는 여러 새 컨텍스트 키워드( 즉, 식별자)가 포함됩니다. 특히 다음과 같습니다. from, join, on, equals, into, let, orderby, ascending, descending, select, group and by. 이러한 식별자를 쿼리 식에서 키워드 또는 단순 이름으로 혼합하여 발생하는 모호성을 방지하기 위해 쿼리 식 내의 모든 위치에서 키워드로 간주됩니다.

이를 위해 쿼리 식은 "fromidentifier"로 시작하고 ";",= "" 또는 ","를 제외한 모든 토큰으로 시작하는 식입니다.

이러한 단어를 쿼리 식 내의 식별자로 사용하려면 "@"(§2.4.2)로 접두사를 지정할 수 있습니다.

26.7.1 쿼리 식 변환

C# 3.0 언어는 쿼리 식의 정확한 실행 의미 체계를 지정하지 않습니다. 대신 C# 3.0은 쿼리 식을 쿼리 식 패턴을 준수하는 메서드 호출로 변환합니다. 특히 쿼리 식은 §26.7.2에 설명된 대로 특정 서명 및 결과 형식을 가질 것으로 예상되는 Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenByDescending, ThenByDescending, GroupByCast라는 메서드의 호출로 변환됩니다. 이러한 메서드는 쿼리되는 개체의 메서드 또는 개체 외부에 있는 확장 메서드를 instance 쿼리의 실제 실행을 구현할 수 있습니다.

쿼리 식에서 메서드 호출로의 변환은 형식 바인딩 또는 오버로드 확인이 수행되기 전에 발생하는 구문 매핑입니다. 번역은 구문적으로 올바르지만 의미상 올바른 C# 코드를 생성하도록 보장되지는 않습니다. 쿼리 식을 변환한 후 결과 메서드 호출은 일반 메서드 호출로 처리되며, 메서드가 존재하지 않는 경우, 인수에 잘못된 형식이 있거나 메서드가 제네릭이고 형식 유추가 실패하는 경우와 같이 오류를 발견할 수 있습니다.

쿼리 식은 더 이상 축소할 수 없을 때까지 다음 번역을 반복적으로 적용하여 처리됩니다. 번역은 우선 순위에 따라 나열됩니다. 각 섹션에서는 이전 섹션의 번역이 완전히 수행되었다고 가정합니다.

특정 변환은 로 표시된 투명한 식별자를 사용하여 범위 변수를 *삽입합니다. 투명 식별자의 특수 속성은 §26.7.1.7에서 자세히 설명합니다.

26.7.1.1 연속이 있는 groupby 절 선택 및 그룹화

연속이 있는 쿼리 식

from ... into x...

은 로 변환됩니다.

from x in ( from ... ) ...

다음 섹션의 번역에서는 쿼리 에 연속이 없다고 가정합니다.

다음 예제는

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 Tx in e

은 로 변환됩니다.

from x in ( e ) . Cast < T > ( )

범위 변수 형식을 명시적으로 지정하는 인 절

join Tx 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 이 아닌 제네릭 IEnumerable<T> 인터페이스를 구현하는 컬렉션을 쿼리하는 데 유용합니다. 위의 예제에서 고객이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)

degenerate 쿼리 식은 원본의 요소를 간단하게 선택하는 식입니다. 번역의 이후 단계에서는 다른 번역 단계에서 도입한 퇴화 쿼리를 원본으로 바꿔 제거합니다. 그러나 쿼리 식의 결과가 원본의 형식과 ID를 쿼리 클라이언트에 표시하므로 원본 개체 자체가 아니라는 것을 확인하는 것이 중요합니다. 따라서 이 단계에서는 소스에서 선택을 명시적으로 호출하여 소스 코드로 직접 작성된 퇴화 쿼리를 보호합니다. 그런 다음 Select 및 기타 쿼리 연산자의 구현자가 이러한 메서드가 원본 개체 자체를 반환하지 않도록 합니다.

26.7.1.4 From, let, where, join 및 orderby 절

두 번째 from 절과 select 절이 있는 쿼리 식

from x1 in e1
from x2 in e2
select v

은 로 변환됩니다.

( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v )

두 번째 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 )
...

에 가 없는 절과 joinselect절이 차례로 있는 쿼리 식

from x1 in e1
join x2 in e2 on k1 equals k2
select v

은 로 변환됩니다.

( e1 ) . Join( e2 , x1 => k1 , x2 => k2 , ( x1 , x2 ) => v )

에 없는 절이 있는 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 })
...

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 )

절이 있는 join 쿼리 식 뒤에 select 절 이외의 항목이 있는 쿼리 식 into

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 절이 내림차순 방향 표시기를 지정하는 경우 OrderByDescending 또는 ThenByDescending 호출이 대신 생성됩니다.

다음 번역에서는 let, where, join 또는 orderby 절이 없고 각 쿼리 식의 절 에서 1개 이하의 초기 절이 없다고 가정합니다.

다음 예제는

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 는 표시되지 않으며 액세스할 수 없는 컴파일러 생성 식별자입니다.

다음 예제는

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 는 표시되지 않으며 액세스할 수 없는 컴파일러 생성 식별자입니다.

다음 예제는

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)

여기서 xy 는 표시되지 않으며 액세스할 수 없는 컴파일러 생성 식별자입니다.

다음 예제는

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 절 선택

폼의 쿼리 식

from x in e select v

은 로 변환됩니다.

( e ) . Select ( x => v )

v가 식별자 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가 식별자 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 투명한 식별자

특정 번역은 로 표시된 투명한 식별자를 사용하여 범위 변수를 *삽입합니다. 투명한 식별자는 적절한 언어 기능이 아닙니다. 쿼리 식 변환 프로세스에서 중간 단계로만 존재합니다.

쿼리 변환이 투명한 식별자를 삽입하는 경우 추가 변환 단계는 투명 식별자를 람다 식 및 익명 개체 이니셜라이저로 전파합니다. 이러한 컨텍스트에서 투명 식별자는 다음과 같은 동작을 수행합니다.

  • 투명 식별자가 람다 식에서 매개 변수로 발생하는 경우 연결된 익명 형식의 멤버는 람다 식의 본문에 자동으로 scope.
  • 투명한 식별자를 가진 멤버가 scope 있는 경우 해당 멤버도 scope.
  • 익명 개체 이니셜라이저에서 투명 식별자가 멤버 선언자로 발생하면 투명한 식별자가 있는 멤버가 도입됩니다.

다음 예제는

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 })

투명 식별자가 지워질 때 은 과 같습니다.

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 는 표시되지 않으며 액세스할 수 없는 컴파일러 생성 식별자입니다.

다음 예제는

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, yz 는 표시되지 않으며 액세스할 수 없는 컴파일러 생성 식별자입니다.

26.7.2 쿼리 식 패턴

쿼리 식 패턴은 형식이 쿼리 식을 지원하기 위해 구현할 수 있는 메서드 패턴을 설정합니다. 쿼리 식은 구문 매핑을 통해 메서드 호출로 변환되므로 형식은 쿼리 식 패턴을 구현하는 방법에 상당한 유연성이 있습니다. 예를 들어 패턴의 메서드는 두 메서드가 동일한 호출 구문을 가지므로 instance 메서드 또는 확장 메서드로 구현할 수 있으며, 람다 식은 둘 다로 변환할 수 있으므로 메서드가 대리자 또는 식 트리를 요청할 수 있습니다.

쿼리 식 패턴을 지원하는 제네릭 형식 C<T> 의 권장되는 모양은 다음과 같습니다. 제네릭 형식은 매개 변수와 결과 형식 간의 적절한 관계를 설명하기 위해 사용되지만 제네릭이 아닌 형식에 대해서도 패턴을 구현할 수 있습니다.

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; }
}

위의 메서드는 제네릭 대리자 형식 Func<T1, R>Func<T1, T2, R>을 사용하지만 매개 변수 및 결과 형식에서 동일한 관계를 가진 다른 대리자 또는 식 트리 형식을 똑같이 잘 사용할 수 있습니다.

OrderBy 또는 OrderByDescending의 결과에서만 ThenByThenByDescending 메서드를 사용할 수 있도록 하는 C<T와 O T> 간의 권장 관계를 확인합니다.>< 또한 각 내부 시퀀스에 추가 Key 속성이 있는 시퀀스 시퀀스인 GroupBy 결과의 권장 셰이프도 확인합니다.

표준 쿼리 연산자(별도의 사양에 설명됨)는 System.Collections.Generic.IEnumerable<T> 인터페이스를 구현하는 모든 형식에 대한 쿼리 연산자 패턴의 구현을 제공합니다.

26.8 식 트리

식 트리를 사용하면 람다 식을 실행 코드 대신 데이터 구조로 나타낼 수 있습니다. 대리자 형식 D 로 변환할 수 있는 람다 식도 System.Query.Expression<D> 형식의 식 트리로 변환할 수 있습니다. 람다 식을 대리자 형식으로 변환하면 실행 코드가 생성되고 대리자가 참조하는 반면 식 트리 형식으로 변환하면 식 트리 instance 만드는 코드가 내보내집니다. 식 트리는 람다 식의 효율적인 메모리 내 데이터 표현이며 식의 구조를 투명하고 명시적으로 만듭니다.

다음 예제에서는 실행 코드와 식 트리로 모두 람다 식을 나타냅니다. 변환이 Func<int,int로 존재하기 때문에 식 Func int,int>로의 변환도 존재합니다>>.<<

Func<int,int> f = x => x + 1;                  // Code
Expression<Func<int,int>> e = x => x + 1;      // Data

이러한 할당에 따라 대리자 fx + 1을 반환하는 메서드를 참조하고 식 트리 ex + 1 식을 설명하는 데이터 구조를 참조합니다.

26.8.1 오버로드 확인

오버로드 확인을 위해 식<D> 형식에 대한 특수 규칙이 있습니다. 특히 다음 규칙이 더 나은 정의에 추가됩니다.

  • 식<D1>D2>보다 더 나은 경우에만 D1이 식<D2보다 낫 습니다.

식<D>와 대리자 형식 사이에는 더 나은 규칙이 없습니다.

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; } }
}

자동으로 구현(자동 구현) 속성은 이 패턴을 자동화합니다. 더 구체적으로, 비 추상 속성 선언은 세미콜론 접근자 본문을 가질 수 있습니다. 두 접근자 모두 있어야 하며 둘 다 세미콜론 본문이 있어야 하지만 접근성 한정자가 다를 수 있습니다. 이와 같이 속성을 지정하면 속성에 대한 지원 필드가 자동으로 생성되고 접근자가 해당 지원 필드에서 읽고 쓰도록 구현됩니다. 지원 필드의 이름은 컴파일러가 생성되고 사용자에게 액세스할 수 없습니다.

다음 선언은 위의 예제와 동일합니다.

 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; }
}

또한 이 제한은 속성 자체에 할당하려면 구조체를 확실히 할당해야 하므로 구조체의 표준 생성자를 사용하여 구조체 형식의 명확한 할당을 수행할 수 있음을 의미합니다.