C# 프로그래밍 언어에 추후 추가되는 기능

Prashant Sridharan
Microsoft Corporation

2003년 3월

적용 대상:
Microsoft Visual C#

요약: Microsoft Corporation에서는 다음 주요 버전의 C# 언어를 개발하고 있습니다. 이 기사에는 generics, iterator, anonymous 메서드 및 partial 형식을 비롯한 4가지 새로운 핵심 기능에 대한 정보가 포함되어 있습니다(15페이지/인쇄 페이지 기준).

목차

소개
Generics
Iterator
Anonymous 메서드
Partial 형식
표준 규격
가용성
추가 정보

소개

C#은 산업 및 개발 언어에서 공통적인 기능을 찾아 통합한 혁신적인 최신 프로그래밍 언어입니다. C# 언어의 디자인 철학을 바탕으로, Microsoft는 언어 구문을 사용하는 개발자의 생산성을 높여 주는 C# 언어에 대한 몇 가지 새로운 기능을 소개합니다.

Microsoft C#

2001년 2월 소개된 이래, 많은 개발자들이 C# 프로그래밍 언어를 사용하여 소프트웨어를 작성하기 시작했습니다. Microsoft 내에서도 .NET Framework, MSN Web 속성, Tablet PC SDK 등을 포함한 몇 가지 응용 프로그램을 작성하는 데 C#을 사용했습니다. 따라서 C#은 고품질의 상업용 소프트웨어 구성에 적합한 언어임이 입증된 셈입니다.

C# 언어의 대부분의 기능은 4가지 디자인 목표를 기반으로 만들어졌습니다.

언어에서 값 및 참조 형식을 사용하는 방식을 간소화하며 통합된 형식의 시스템입니다.

XML 주석, 특성, 속성,이벤트 및 대리자와 같은 기능을 통해 구축된 구성 요소 기반의 디자인입니다.

안전한 포인터 조작, 오버플로 검사 등과 같은 C# 언어의 고유한 기능을 통해 구축된 유용한 개발자 헤드룸입니다.

개발자의 생산성을 향상시켜 주는 실용적인 언어 구문(예: foreach, using 명령문)입니다.

Microsoft는 "Visual Studio for Yukon" 버전의 C# 언어로 여러 가지 개발 및 산업 언어의 다양한 기능을 통합하여 뛰어난 표현 구문을 토대로 작성하기 위한 계획을 세우고 있습니다. 이러한 언어에 포함된 기능은 generics, iterator, anonymous 메서드 및 partial 형식입니다.

추후 추가되는 기능

통합된 형식 시스템, 구성 요소 기반 개발, 개발자 헤드룸 및 실용적인 언어 구문이 핵심 사항이며, C#의 추후 개발에서도 이러한 요소를 기반으로 합니다. 다음은 Microsoft가 다음 주요 버전의 C# 언어에 제공하기 위해 구상 중인 4가지 핵심 기능을 요약한 것입니다. 이러한 기능의 디자인은 아직 완료되지 않았으며 Microsoft Corporation은 개발자 커뮤니티에서 의견을 수렴하고 있습니다.

Generics

프로젝트가 복잡해질수록 프로그래머는 기존의 구성 요소 기반 소프트웨어를 다시 사용하고 사용자 지정할 수 있는 방법을 더욱 필요로 합니다. 다른 언어에서 코드를 최대한 다시 사용하기 위해 프로그래머는 대개 generics라는 기능을 사용합니다. C#에는 형식이 안전한 고성능 버전의 generics가 포함됩니다. 이 generics는 C++의 템플릿 및 Java 언어용 generics의 구문과 약간 다르며 구현 방식은 크게 다릅니다.

현재의 Generic 클래스 작성

현재의 C#에서 프로그래머는 기본 개체 형식의 인스턴스에 데이터를 저장하여 제한된 버전의 generic 형식을 만들 수 있습니다. C#의 모든 개체는 기본 개체 형식에서 상속되고 통합된 .NET 형식 시스템의 boxing 및 unboxing 기능이 있으므로, 프로그래머는 참조 및 값 형식 모두를 개체 형식의 변수에 저장할 수 있습니다. 하지만 참조 형식, 값 형식 및 기본 개체 형식 사이를 변환하는 데 심각한 성능 저하가 발생합니다.

이를 설명하기 위해 다음 코드 샘플에서는 "Push" 및 "Pop"의 두 가지 작업을 수행하는 간단한 스택 형식을 만듭니다. Stack 클래스는 개체 형식의 배열에 해당 데이터를 저장하고 Push 및 Pop 메서드는 기본 개체 형식을 사용하여 각각 데이터를 받고 반환합니다.

public class Stack
{
private object[] items = new object[100];

public void Push(object data)
{
...
}

public object Pop()
{
...
}
}

그런 다음 사용자 지정 형식(예: Customer 형식)을 스택에 푸시할 수 있습니다. 하지만 프로그램에서 데이터를 검색해야 하는 경우 기본 개체 형식인 Pop 메서드의 결과를 Customer 형식에 명시적으로 캐스팅해야 합니다.

Stack s = new Stack();
s.Push(new Customer());
Customer c = (Customer) s.Pop();

정수 등과 같은 값 형식이 Push 메서드에 전달되면 런타임은 이 형식을 참조 형식으로 자동 변환(boxing 프로세스)한 다음 내부 데이터 구조에 저장합니다. 마찬가지로, 프로그램에서 정수 등과 같은 값 형식을 스택에서 검색하려면 Pop 메서드에서 얻은 개체 형식을 값 형식에 명시적으로 캐스팅해야 합니다(unboxing 프로세스).

Stack s = new Stack();
s.Push(3);
int i = (int) s.Pop();

값 및 참조 형식 사이의 boxing 및 unboxing은 특히 부담되는 작업입니다.

또한 현재 구현에서는 해당 데이터를 스택에 넣을 수 없습니다. 실제로, 스택을 만들 수 있으며 Customer 형식을 스택에 푸시할 수 있습니다. 다음 예제처럼 동일한 스택을 사용할 수 있으며 데이터를 꺼내고 이 데이터를 다른 형식에 캐스팅할 수 있습니다.

Stack s = new Stack();
s.Push(new Customer());

Employee e = (Employee) s.Pop();

하지만 이전 코드 샘플은 구현하려는 단일 형식의 Stack 클래스 사용이 적절하지 않은 경우이며 오류가 발생하지만 실제로는 코드 자체에는 이상이 없고 컴파일러에서도 문제가 없습니다. 그러나 런타임 시 잘못된 캐스트 연산으로 인해 프로그램에 오류가 발생합니다.

Generics 만들기 및 소비

C#의 generics는 사용하는 형식 기반의 컴파일러에 의해 특수화된 고성능 데이터 구조를 만드는 기능을 제공합니다. 내부 알고리즘이 동일하게 유지되고 내부 데이터 형식이 최종 사용자 설정에 따라 다를 수 있도록 매개 변수 있는 형식을 만듭니다.

C++와 거의 동일한 방식으로 C#의 generics가 선언되므로 개발자가 쉽게 이해할 수 있습니다. 일반적으로 프로그래머는 기존 클래스 및 구조와 똑같이 만들 수 있으며 꺾쇠 괄호 표기법(< 및 >)을 사용하여 형식 매개 변수를 지정할 수 있습니다. 클래스를 사용하면 각 매개 변수는 해당 클래스의 사용자가 제공하는 실제 형식으로 바꿔야 합니다.

다음 예제에서는 클래스를 선언한 후 ItemType이라는 형식 매개 변수가 꺾쇠 괄호에 지정되고 선언된 위치에 Stack 클래스를 만듭니다. Generic Stack 클래스의 인스턴스는 기본 개체 형식 사이를 변환하는 것이 아니라, 만들어진 형식을 받아들이고 해당 형식의 데이터를 저장합니다. 형식 매개 변수인 ItemType은 인스턴스화하는 동안 해당 형식이 지정될 때까지 프록시처럼 기능하고, 내부 항목 배열의 형식(Push 메서드에 대한 매개 변수 형식 및 Pop 메서드에 대한 반환 형식)으로 사용됩니다.

public class Stack<ItemType>
{
private ItemType[] items;

public void Push(ItemType data)
{
...
}

public ItemType Pop()
{
...
}
}

프로그램에서 Stack 클래스를 사용하면 다음 예제처럼 generic 클래스에서 사용하는 실제 형식을 지정할 수 있습니다. 이 경우 인스턴스화 문에 꺾쇠 괄호 표기법을 사용하여 기본 정수 형식을 매개 변수로 지정하여 이 형식을 사용하도록 Stack 클래스에 지시합니다.

Stack<int> stack = new Stack<int>();
stack.Push(3);
int x = stack.Pop();

이렇게 하면 프로그램에서 Stack 클래스의 새 인스턴스를 만들고 각 ItemType은 제공된 정수 매개 변수로 대체됩니다. 프로그램에서 정수 매개 변수로 Stack 클래스의 새 인스턴스를 만들면 Stack 클래스 내에서 항목 배열의 기본 저장소는 개체가 아니라 정수가 됩니다. 또한 정수를 스택에 푸시하는 작업과 관련된 boxing 문제가 해결됩니다. 또한 프로그램에서 항목을 스택에 꺼내는 경우, Stack 클래스의 특정 인스턴스가 정수를 해당 데이터 구조에 저장하므로 이 항목을 적절한 형식에 명시적으로 캐스팅할 필요가 없습니다.

프로그램에서 정수 이외의 항목을 Stack 클래스에 저장하도록 하려면 새 형식을 매개 변수로 지정하여 Stack 클래스의 새 인스턴스를 만들어야 합니다. 단순 Customer 형식이 있다고 가정하면 이 형식을 저장하는 데 Stack 개체를 사용할 수 있습니다. 이렇게 하려면 형식 매개 변수로 Customer 개체를 사용하여 Stack 클래스를 인스턴스화하고 프로그램 코드를 다시 사용하면 됩니다.

Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
Customer c = stack.Pop();

매개 변수로 Customer 형식을 사용하여 Stack 클래스를 만들면 Customer 형식만 스택에 저장할 수 있습니다. C#의 generics는 완전히 형식화되므로 다음 예제처럼 더 이상 정수를 스택에 제대로 저장할 수 없습니다.

Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
stack.Push(3) // 컴파일 시 오류
Customer c = stack.Pop(); // 캐스트가 필요하지 않습니다.

Generics의 장점

Generics를 사용하면 프로그래머는 코드의 작성, 테스트 및 배포를 한 번에 수행할 수 있으므로 다양한 데이터 형식에 해당 코드를 다시 사용할 수 있습니다. 이러한 사항은 첫 번째 스택 예제를 통해 알 수 있으며, 두 번째 스택 예제를 사용하면 응용 프로그램의 성능에 영향을 거의 미치지 않는 코드를 다시 사용할 수 있습니다. 값 형식의 경우, 첫 번째 스택 예제는 심각한 성능 저하 현상을 내포하고 있으며, 두 번째 스택 예제에서는 boxing 및 downcast를 제거했기 때문에 성능 저하 문제가 완전히 해결됩니다.

또한 generics는 컴파일 시 검사됩니다. 프로그램이 제공된 형식 매개 변수로 generic 클래스를 인스턴스화하는 경우 형식 매개 변수는 프로그램에서 클래스 정의에 지정한 형식으로만 사용할 수 있습니다. 예를 들어 프로그램에서 Customer 개체의 스택을 만든 경우에는 더 이상 정수를 스택에 푸시할 수 없습니다. 이러한 동작을 적용하면 보다 더 신뢰할 수 있는 코드를 작성할 수 있습니다.

또한 generics의 C# 구현은 다른 강력한 형식의 구현과 비교해 보면 코드 비대화를 감소시킵니다. Generics로 형식화된 컬렉션을 만들면 각 클래스의 특정 버전을 만들 필요가 없으며 성능을 그대로 유지할 수 있습니다. 예를 들어 프로그램에서 매개 변수 있는 Stack 클래스 하나를 만들 수 있는 경우, 정수를 저장하는 IntegerStack, 문자열을 저장하는 StringStack 또는 Customer 형식을 저장하는 CustomerStack을 만들지 않아도 됩니다.

또한 코드를 더 쉽게 읽을 수 있습니다. Stack 클래스 하나를 만들면, 프로그램은 클래스 하나에 스택과 관련된 모든 동작을 쉽게 캡슐화할 수 있습니다. 그런 다음 Customer 형식의 스택을 만들면 프로그램은 이 스택에 저장된 Customer 형식이기는 하지만 스택 데이터 구조를 사용합니다.

여러 형식 매개 변수

Generic 형식은 다양한 매개 변수 형식을 사용할 수 있습니다. 앞에서 설명한 스택 예제에서는 한 개의 형식만 사용했습니다. 키 옆에 값이 저장된 간단한 Dictionary 클래스를 만든 것으로 가정합니다. 프로그램은 클래스 정의의 꺾쇠 괄호 내에 쉼표로 구분된 두 개의 매개 변수를 선언하여 Dictionary 클래스의 generic 버전을 정의할 수 있습니다.

public class Dictionary<KeyType, ValType>
{
public void Add(KeyType key, ValType val)
{
...
}

public ValType this[KeyType key]
{
...
}
}

Dictionary 클래스를 사용하는 경우, 인스턴스화 문의 꺾쇠 괄호 내에 여러 개의 매개 변수를 쉼표로 구분하여 입력해야 하며, Add 함수 및 인덱서의 매개 변수에 대해 올바른 형식을 제공해야 합니다.

Dictionary<int, Customer> dict = new Dictionary<int, Customer>();
dict.Add(3, new Customer());
Customer c = dict.Get[3];

제약 조건

일반적으로 프로그램에서는 지정된 형식 매개 변수를 기반으로 하는 데이터 저장 이외의 작업을 하게 됩니다. 프로그램에서 형식 매개 변수의 멤버를 사용하여 프로그램의 generic 형식 내에서 명령문을 실행하도록 하려는 경우도 종종 있습니다.

제약 조건이 필요한 이유

예를 들어 제공된 키의 CompareTo 메서드를 사용하여 Dictionary 클래스의 Add 메서드에서 항목을 비교하려고 하는 경우를 가정합니다. 예를 들면 다음과 같습니다.

public class Dictionary<KeyType, ValType>
{
public void Add(KeyType key, ValType val)
{
...
switch(key.CompareTo(x))
{
}
...
}
}

하지만 컴파일 시 형식 매개 변수 KeyType은 예상한 것처럼 generic입니다. 있는 그대로 작성하면 컴파일러는 ToString과 같은 기본 개체 형식에 사용 가능한 작업만 형식 매개 변수의 키 인스턴스 KeyType에 사용 가능한 것으로 가정합니다. 그 결과, 컴파일러는 CompareTo 메서드가 정의되지 않았기 때문에 컴파일 오류를 표시합니다. 그러나 프로그램은 IComparable 인터페이스와 같은 CompareTo 메서드를 포함하는 개체에 키 변수를 캐스팅할 수 있습니다. 다음 예제에서는 키라고 하는 매개 변수 형식의 인스턴스 KeyType을 IComparable 인터페이스에 명시적으로 캐스팅하여 프로그램이 컴파일할 수 있게 합니다.

public class Dictionary<KeyType, ValType>
{
public void Add(KeyType key, ValType val)
{
...
switch(((IComparable) key).CompareTo(x))
{
}
...
}
}

그러나 Dictionary 클래스를 인스턴스화하고 IComparable 인터페이스를 구현하지 않는 형식 매개 변수를 제공하면 프로그램에서 런타임 오류, 특히 InvalidCastException이 발생합니다.

제약 조건 선언

C#에서 프로그램은 generic 클래스에 선언된 각 형식 매개 변수에 대한 제약 조건의 선택적 목록을 제공할 수 있습니다. 제약 조건이란 형식이 generic 형식을 구성하기 위해 이행해야 할 요구 사항을 나타냅니다. 제약 조건은 where 키워드 다음에 "매개 변수-요구 사항" 쌍을 사용하여 선언합니다. 여기서 "매개 변수"는 generic 형식에 정의된 매개 변수 중의 하나여야 하며 "요구 사항"은 클래스 또는 인터페이스여야 합니다.

Dictionary 클래스에서 CompareTo 메서드를 사용하기 위한 요구 사항을 충족시키기 위해, 프로그램에서 KeyType 형식 매개 변수에 제약 조건을 설정할 수 있으며 IComparable 인터페이스를 구현하는 데 Dictionary 클래스의 첫 번째 매개 변수에 전달된 모든 형식을 필요로 합니다. 예를 들면 다음과 같습니다.

public class Dictionary<KeyType, ValType> where KeyType : IComparable
{
public void Add(KeyType key, ValType val)
{
...
switch(key.CompareTo(x))
{
}
...
}
}

이제 컴파일하면 코드를 검사하여 매번 프로그램이 Dictionary 클래스를 사용하는지 확인하고 IComparable 인터페이스를 구현하는 형식을 첫 번째 매개 변수로 전달합니다. 또한 프로그램은 CompareTo 메서드를 호출하기 전에 변수를 IComparable 인터페이스에 명시적으로 캐스팅할 필요가 없습니다.

여러 제약 조건

지정된 형식 매개 변수의 경우 프로그램에서 제약 조건으로서 원하는 만큼 인터페이스를 지정할 수 있지만 클래스는 하나만 지정할 수 있습니다. 각각의 새 제약 조건은 다른 매개 변수-요구 사항 쌍으로 선언되며, 지정된 generic 형식의 각 제약 조건이 쉼표로 구분됩니다. 다음 예제에서 Dictionary 클래스는 KeyType 및 ValType이라는 두 개의 매개 변수 형식을 포함하고 있습니다. KeyType 형식 매개 변수에는 인터페이스 제약 조건 두 개가 있으며, ValType 형식 매개 변수에는 클래스 제약 조건 한 개가 있습니다.

public class Dictionary<KeyType, ValType> where
KeyType : IComparable,
KeyType : IEnumerable,
ValType : Customer
{
public void Add(KeyType key, ValType val)
{
...
switch(key.CompareTo(x))
{
}
...
}
}

런타임 시 Generics

Generic 클래스가 컴파일되면 이 클래스는 일반 클래스와 실제로 차이가 없습니다. 사실상 컴파일의 결과는 메타데이터 및 중간 언어(IL)일 뿐입니다. 물론 IL은 코드의 어느 위치에서든 사용자 제공 형식을 받아들일 수 있도록 매개 변수화됩니다. Generic 형식의 IL은 제공된 형식 매개 변수가 값 형식 또는 참조 형식인지의 여부에 따라 다릅니다.

Generic 형식이 매개 변수로서 값 형식으로 처음 구성된 경우, 런타임은 IL의 적절한 위치에 대체되는 제공된 매개 변수(또는 매개 변수)를 사용하여 특수화된 generic 형식을 만듭니다. 특수화된 generic 형식은 매개 변수로서 사용되는 각각의 유일한 값 형식에 대해 한 번만 만들어집니다.

예를 들어 프로그램 코드에서 정수로 구성된 Stack을 선언했다고 가정합니다.

Stack<int> stack;

여기서, 런타임은 해당 매개 변수에 대해 적절하게 대체된 정수를 사용하여 Stack 클래스의 특수화된 버전을 생성합니다. 이제 프로그램 코드가 정수의 스택을 사용할 때마다 런타임은 생성된 특수한 Stack 클래스를 다시 사용합니다. 다음 예제에서는 정수의 스택 인스턴스 두 개를 만들며, 둘 다 런타임에서 정수의 스택에 대해 미리 생성한 코드를 사용합니다.

Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();

그러나 프로그램 코드의 다른 지점에서 다른 Stack 클래스(매개 변수로서 long 또는 사용자 정의 구조와 같은 다른 값 형식)를 만들면 런타임은 IL의 적절한 위치에 long을 대체하는 다른 버전의 generic 형식을 생성합니다. 값 형식으로 구성된 generics에 대해 특수화된 클래스를 생성하면 성능이 향상되는 장점이 있습니다. 즉, 각각의 특수화된 generic 클래스에 "기본적으로" 값 형식이 들어 있으므로 변환 작업이 더 이상 필요하지 않습니다.

Generics는 참조 형식에 대해 약간 다르게 작동합니다. Generic 형식이 참조 형식으로 구성되면 런타임은 IL의 매개 변수에 대해 대체된 개체 참조가 있는 특수화된 generic 형식을 만듭니다. 구성된 형식이 자신의 매개 변수를 참조 형식으로 하여 인스턴스화될 때마다 그 형식에 관계 없이 런타임은 이전에 만든 특수한 generic 형식 버전을 다시 사용합니다.

예를 들어 Customer 클래스 및 Order 클래스의 두 가지 참조 형식이 있고 이후 Customer 형식의 스택을 만든 것으로 가정합니다.

Stack<Customer> customers;

이런 경우, 런타임은 특수화된 Stack 클래스 버전을 생성하고, 데이터를 저장하는 대신 나중에 채울 개체 참조를 저장합니다. 코드의 다음 행에서 Order라는 다른 참조 형식의 스택을 만든다고 가정합니다.

Stack<Order> orders = new Stack<Order>();

값 형식과는 다르게, 특수화된 다른 Stack 클래스 버전이 Order 형식에 대해 만들어지지 않습니다. 반대로 특수화된 Stack 클래스 버전의 인스턴스가 만들어지고 orders 변수가 이 인스턴스를 참조하도록 설정됩니다. 매개 변수의 위치에 대체된 각각의 개체 참조의 경우, Order 형식의 크기인 메모리 영역이 할당되고 포인터가 해당 메모리 위치를 참조하도록 설정됩니다. 그런 다음 Customer 형식의 스택을 만들기 위한 코드 행이 있다고 가정합니다.

customers = new Stack<Customer>();

Order 형식으로 만든 이전의 Stack 클래스를 사용하므로 특수화된 Stack 클래스의 다른 인스턴스가 만들어지고 그 안에 포함된 포인터는 Customer 형식의 메모리 크기 영역에 대한 참조로 설정됩니다. 참조 형식의 수는 프로그램마다 매우 다양하므로, generics의 C# 구현은 참조 형식의 generic 클래스에 대해 컴파일러가 만드는 특수화된 클래스의 수를 줄임으로써 코드 비대화를 상당히 감소시킵니다.

또한 generic C# 클래스가 형식 매개 변수로 인스턴스화되는 경우, 값 또는 참조 형식이 됩니다. 런타임 시 리플렉션을 사용하여 쿼리할 수 있으며 형식 매개 변수뿐만 아니라 실제 형식 모두를 확인할 수 있습니다.

C# Generics와 다른 구현 간의 차이점

C++ 템플릿은 C# generics와는 현저하게 다릅니다. C# generics는 IL에 컴파일되므로 각각의 값 형식에 대해서는 런타임 시, 참조 형식에 대해서는 한 번만 특수화가 인텔리전트 방식으로 발생하게 합니다. C++ 템플릿은 템플릿에 제공된 각 형식 매개 변수에 대한 특수화된 형식을 생성하는 코드 확장 매크로입니다. 따라서 C++ 컴파일러는 정수의 스택과 같은 템플릿을 발견하면 기본 형식으로서 정수를 내부적으로 포함하는 Stack 클래스로 템플릿 코드를 확장합니다. 형식 매개 변수가 값 또는 참조 형식에 관계없이 코드 비대화를 감소시킬 수 있도록 링커를 디자인하지 않으면 C++ 컴파일러는 매번 특수화된 클래스를 만듭니다. 따라서 C# generics에 대한 코드 비대화가 상당히 증가됩니다.

또한 C++ 템플릿에서는 제약 조건을 정의할 수 없습니다. C++ 템플릿은 형식 매개 변수에 속해 있거나 속해 있지 않은 멤버를 사용하여 제약 조건을 암시적으로 정의만 할 수 있습니다. 결과적으로 Generic 클래스에 전달되는 형식 매개 변수에 멤버가 있으면 프로그램은 제대로 작동합니다. 형식 매개 변수에 멤버가 없으면 프로그램은 실패하고 암호 오류 메시지가 반환될 수 있습니다. C# generics는 제약 조건을 선언할 수 있고 엄격한 형식이므로 이러한 잠재적인 오류가 없습니다.

한편, Sun Microsystems는 다음 버전의 Java 언어에 암호명이 "Tiger"인 generics 추가를 제안했습니다. Sun은 Java Virtual Machine을 수정할 필요가 없는 구현을 선택했습니다. 따라서 Sun은 수정되지 않은 가상 시스템에서 generics 구현하는 문제에 직면하게 되었습니다.

제안된 Java 구현은 C++의 템플릿과 형식 매개 변수, 제약 조건 등을 포함한 C#의 generics와 비슷한 구문을 사용합니다. 그러나 이 구현은 참조 형식과 다르게 값 형식을 처리하므로 수정되지 않은 Java Virtual Machine은 값 형식에 대한 generics를 지원할 수 없습니다. 따라서 Java의 generics에서는 실행 효율성을 얻을 수 없습니다. Java 컴파일러는 제약 조건이 선언된 경우 지정된 제약 조건에서 자동 downcast를 삽입하고, 제약 조건이 선언되지 않은 경우에는 데이터를 반환할 때마다 기본 개체 형식을 삽입합니다. 또한 Java 컴파일러는 컴파일 시 하나의 특수화된 형식을 생성하며, 이 형식은 구성된 모든 형식을 인스턴스화하는 데 사용됩니다. 마지막으로, Java Virtual Machine은 기본적으로 generics를 지원하지 않으므로 런타임 시에 generic 형식의 인스턴스에 대한 형식 매개 변수를 확인할 수 있는 방법이 없으며 다른 리플렉션 사용이 엄격하게 제한됩니다.

다른 언어에서의 Generics 지원

Microsoft는 Visual J#, Visual C++ 및 Visual Basic에서의 generic 형식 사용 및 작성을 지원하려고 합니다. 일부 언어에서는 다른 언어보다 먼저 이 기능을 구현할 수도 있으므로 Microsoft의 다른 3개 언어 모두에서 generics 지원을 포함할 계획입니다. C# 팀은 기본 런타임에 generics 기능을 포함시키는 다중 언어 지원에 대한 토대를 마련하고 있습니다. Microsoft는 .NET 기반 언어에서 generics 작성 및 소비를 보장하기 위해 타사 언어 파트너와도 긴밀하게 협력하고 있습니다.

Iterator

Iterator는 CLU, Sather 및 Icon과 같은 개발 언어에서 사용되는 비슷한 기능을 기반으로 하는 언어 구문입니다. Iterator는 해당 요소에서 foreach 문이 반복되는 방식을 형식이 간단하게 선언할 수 있게 만듭니다.

Iterator가 필요한 이유

클래스에서 foreach 루프 구문을 사용하여 반복을 지원하려면 "열거자 패턴"을 구현해야 합니다. 예를 들어 왼쪽의 foreach 루프 구문은 컴파일러에 의해 오른쪽의 while 루프 구문으로 확장됩니다.

List list = ...;

foreach(object obj in list)
{
DoSomething(obj);
}

Enumerator e = list.GetEnumerator();

while(e.MoveNext())
{
object obj = e.Current;
DoSomething(obj);

인스턴스가 반복되는 List 데이터 구조는 foreach 루프가 작동하도록 GetEnumerator 함수를 지원해야 합니다. List 데이터 구조가 만들어지므로 ListEnumerator 개체를 반환하는 GetEnumerator 함수가 구현되어야 합니다.

public class List
{
internal object[] elements;
internal int count;

public ListEnumerator GetEnumerator()
{
return new ListEnumerator(this);
}
}

ListEnumerator 개체는 Current 속성 및 MoveNext 메서드를 구현해야 하며, 루프가 반복할 때마다 프로그램이 다음 항목으로 이동할 수 있도록 내부 상태도 관리해야 합니다. 이러한 내부 상태 시스템은 List 데이터 구조에서는 간단할 수 있지만, 이진 트리와 같이 재귀적 이동이 필요한 데이터 구조의 경우 상태 시스템은 매우 복잡해질 수 있습니다.

이러한 열거자 패턴을 구현하려면 개발자에게 매우 많은 작업과 방대한 코드가 필요하기 때문에 C#에는 foreach 루프가 내용을 반복하는 방법을 클래스가 간단하게 지시할 수 있게 하는 새로운 구문이 포함됩니다.

Iterator 정의

Iterator는 foreach 루프 구문의 논리적 구문으로, foreach 키워드 다음에 여는 괄호 및 닫는 괄호를 사용하여 함수와 유사하게 정의됩니다. 다음 예제에서 프로그램은 List 형식에 대해 iterator를 선언합니다. Iterator의 반환 형식은 사용자가 결정하지만 List 클래스는 개체 형식을 내부적으로 저장하므로 아래의 iterator 예제의 반환 형식은 개체가 됩니다.

public class List
{
internal object[] elements;
internal int count;

public object foreach()
{
}
}

열거자 패턴이 구현된 후에는 프로그램이 데이터 구조에 있는 위치를 추적하기 위해 해당 프로그램에서 내부 상태 시스템을 관리해야 합니다. Iterator에는 기본 제공 상태 시스템이 있습니다. 새 yield 키워드를 사용하면 프로그램은 iterator를 호출한 foreach 문에 값을 다시 반환할 수 있습니다. 다음에 foreach 문이 반복되고 iterator를 다시 호출하면 iterator는 이전 yield 문이 있는 위치에서 실행을 시작합니다. 다음 예제에서 프로그램은 3가지 문자열 형식을 생성합니다.

public class List
{
internal object[] elements;
internal int count;

public string foreach()
{
yield "microsoft";
yield "corporation";
yield "개발자 부서";
}
}

다음 예제에서는 이 iterator를 호출하는 foreach 루프가 이전 세 개의 yield 문에 의해 지정된 순서로 문자열을 받을 때마다 세 번 실행합니다.

List list = new List();
foreach(string s in list)
{
Console.WriteLine(s);
}

프로그램에서 iterator를 구현하여 목록에 있는 요소를 이동하도록 하려면 각 반복의 배열에서 각 항목을 생성하는 foreach 루프를 사용하여 요소 배열에서 이동하도록 iterator를 수정합니다.

public class List
{
internal object[] elements;
internal int count;

public object foreach()
{
foreach(object o in elements)
{
yield o;
}
}
}

Iterator가 작동하는 방식

Iterator는 프로그램 대신에 열거자 패턴을 구현하여 번거로운 작업을 처리합니다. 클래스를 만들고 상태 시스템을 작성하는 것이 아니라, C# 컴파일러는 열거자 패턴을 사용하여 iterator에 작성한 코드를 적절한 클래스 및 코드로 변환합니다. 이렇게 함으로써 iterator는 개발자 생산성을 상당한 증가시킵니다.

Anonymous 메서드

Anonymous 메서드는 유용한 언어 구문으로, 프로그래머는 대리자에 캡슐화하고 나중에 수행할 수 있는 코드 블록을 만들 수 있습니다. 이 메서드는 lambda 함수라는 언어 개념을 기반으로 하며 Lisp 및 Python에서 찾을 수 있는 개념과 비슷합니다.

대리자 코드 작성

대리자는 메서드를 참조하는 개체입니다. 대리자가 실행될 때마다 이 대리자가 참조하는 메서드가 호출됩니다. 다음 예제에서는 목록 상자, 텍스트 상자 및 단추의 3가지 컨트롤을 사용하는 간단한 폼을 나타낸 것입니다. 단추가 초기화되면 프로그램은 Click 대리자가 개체에 저장된 AddClick 메서드를 참조하도록 지시합니다. AddClick 메서드에서 텍스트 상자의 값은 목록 상자에 저장됩니다. 단추 인스턴스의 Click 대리자에 추가되기 때문에 단추를 클릭할 때마다 AddClick 메서드가 호출됩니다.

public class MyForm
{
ListBox listBox;
TextBox textBox;
Button button;

public MyForm()
{
listBox = new ListBox(...);
textBox = new TextBox(...);
button = new Button(...);
button.Click += new EventHandler(AddClick);
}

void AddClick(object sender, EventArgs e)
{
listBox.Items.Add(textBox.Text);
}
}

Anonymous 메서드 사용

이전 예제는 매우 간단한 예입니다. 별도의 함수가 만들어지고 대리자에 의해 참조되고 대리자가 호출될 때마다 프로그램이 해당 함수를 호출합니다. 함수 내에서 일련의 실행 단계가 수행됩니다. Anonymous 메서드를 추가하면 프로그램은 클래스에 대해 완전히 새로운 메서드를 만들 수 있으며 대리자에서 그 안에 포함된 실행 단계를 직접 참조할 수 있습니다. Anonymous 메서드는 실행 영역을 나타내는 중괄호 쌍이 있는 인스턴스화 문과 명령문 다음에 대리자를 인스턴스화하여 선언합니다.

다음 예제에서 프로그램은 프로그램 대신에 목록 상자를 수정하는 함수를 참조하는 것이 아니라 대리자 작성 문을 수정하여 목록 상자를 직접 수정합니다. 코드를 저장하여 대리자 작성 문 바로 다음에 있는 실행 범위 내에서 목록 상자를 수정합니다.

public class MyForm
{
ListBox listBox;
TextBox textBox;
Button button;

public MyForm()
{
listBox = new ListBox(...);
textBox = new TextBox(...);
button = new Button(...);
button.Click += new EventHandler(sender, e)
{
listBox.Items.Add(textBox.Text);
};
}
}

Anonymous 메서드 내의 코드가 해당 범위의 외부에 선언된 변수를 액세스하고 조작할 수 있는 방법을 확인하십시오. Anonymous 메서드는 클래스에서 선언한 변수뿐만 아니라 클래스가 상주하는 메서드에 선언된 지역 변수 또는 매개 변수를 참조할 수 있습니다.

매개 변수를 Anonymous 메서드에 전달

또한 Anonymous 메서드 문은 sender 및 e라는 매개 변수 두 개를 포함합니다. Button 클래스의 Click 대리자의 정의를 찾아보면 대리자가 참조하는 모든 함수는 첫 번째는 형식 개체, 두 번째는 EventArgs 형식인 매개 변수 두 개를 포함해야 한다는 것을 알 수 있습니다. Anonymous 메서드를 사용하지 않은 첫 번째 예제에서, 프로그램은 두 개의 매개 변수를 AddClick 메서드, 개체 및 EventArgs에 전달했습니다.

코드가 인라인으로 작성된 경우에도 대리자는 두 개의 매개 변수를 받아야 합니다. Anonymous 메서드에서 두 개의 매개 변수 이름은 관련된 코드 블록에서 사용할 수 있도록 선언해야 합니다. 단추의 Click 이벤트가 트리거되면 Anonymous 메서드를 호출하고 해당 매개 변수를 전달합니다.

Anonymous 메서드가 작동하는 방식

Anonymous 대리자가 발견되면 C# 컴파일러는 해당 실행 범위의 코드를 고유한 이름의 클래스 내에서 고유한 이름의 함수로 자동 변환합니다. 코드 블록에 있는 대리자가 저장된 다음 참조(컴파일러가 생성한 개체 및 메서드)로 설정됩니다. 대리자가 호출되면 Anonymous 메서드 블록은 컴파일러가 생성한 메서드를 통해 실행됩니다.

Partial 형식

한 파일 내에서 형식에 대한 모든 소스 코드를 유지 관리하는 것은 바람직한 개체 지향적 프로그래밍 사례이지만 성능 제약 조건으로 인해 형식의 범위가 커지게 되는 경우가 종종 있습니다. 또한 형식을 하위 형식으로 분리하는 작업은 모든 상황에 적용할 수 없습니다. 게다가 프로그래머는 결과 코드를 수정하여 소스 코드를 내보내는 응용 프로그램을 자주 만들거나 사용합니다. 그러나 소스 코드를 나중에 다시 내보내면 모든 기존 소스 코드를 덮어씁니다.

Partial 형식을 사용하면 간편하게 개발하고 유지 관리할 수 있도록 대량의 소스 코드를 구성하는 형식을 여러 개의 다른 소스 파일로 나눌 수 있습니다. 또한 시스템이 생성한 형식과 사용자가 작성한 형식을 분리할 수 있으므로 도구에서 생성한 코드를 간단하게 보충하거나 수정할 수 있습니다.

다음 예제에서 두 개의 C# 코드 파일(File1.cs 및 File2.cs)은 모두 Foo라는 클래스에 대한 정의를 포함하고 있습니다. Partial 형식을 사용하지 않으면, 두 개의 클래스 모두 동일한 네임스페이스에 존재하므로 컴파일러 오류가 발생합니다. Partial 키워드를 사용하여 이 클래스에 다른 정의가 있을 수도 있다는 것을 컴파일러에 나타낼 수 있습니다.

File1.cs

File2.cs

public partial class Foo

{
public void MyFunction()
{
// 여기에 임의의 명령을 삽입하십시오.
}
}

public partial class Foo

{
public void MyOtherFunction()
{
// 여기에 임의의 명령을 삽입하십시오.
}
}

C# 컴파일러는 컴파일 시 모든 partial 형식의 정의를 수집하고 이를 조합합니다. 컴파일러에서 생성하는 IL은 여러 개의 구성 클래스가 아닌 하나의 조합된 클래스를 표시합니다.

표준 규격

2001년 12월, C# 프로그래밍 언어는 ECMA(European Computer Manufacturer's Association)로부터 표준(ECMA 334)으로 승인을 받았습니다. 그 후에 바로, C# 표준은 ISO(International Organization for Standards)에 "패스트 트랙(fast-track)" 프로세스로 채택되었으며, 곧 승인을 받을 예정입니다. 새 프로그래밍 언어의 발전에 있어서 획기적인 이정표인 C# 표준 작성을 통해 다양한 운영 체제 플랫폼에서 다중 구현을 할 수 있는 가능성을 가져왔습니다. C# 언어의 역사는 짧지만 많은 컴파일러 공급업체 및 연구업체에서 표준을 구현하고 고유 버전의 C# 컴파일러를 만들었습니다.

Microsoft는 C# 언어에 제안하고 싶은 추가 기능에 대한 고객의 의견을 수렴하고 제안한 기능을 개발 진행 중인 표준 프로세스에 적용할 계획입니다.

가용성

여기에서 언급한 기능은 C# 컴파일러의 추후 릴리즈에서 사용할 수 있습니다. 2003년 초에 출시될 Visual Studio .NET 의 "Everett" 릴리즈에는 ECMA 규격에 맞게 약간 수정된 C# 버전이 포함될 예정입니다. 여기서 언급한 기능은 이 버전에 포함되지 않습니다. Microsoft는 출시일이 아직 결정되지 않았지만 이러한 기능을 Visual Studio의 "VS for Yukon" 릴리즈에 포함할 계획입니다.

Microsoft는 몇 개월 안에 전체 사양을 비롯한 해당 기능에 대한 자세한 정보를 제공할 예정이며 이러한 기능 외에도 관심 있는 다른 모든 언어 기능에 대해 프로그래머 및 언어 디자인 커뮤니티의 평가 및 의견을 수렴하고 있습니다. C# 언어 디자이너는 [email-masked] 통해 전자 메일로 의견을 제시할 수 있습니다.

추가 정보

C# 커뮤니티 웹 사이트: http://www.csharp.net

Visual C# 제품 웹 사이트: http://msdn.microsoft.com/vcsharp

화면 맨 위로

최종 수정일: 2003년 5월 13일