19. C# 2.0 소개
C# 2.0에는 여러 언어 확장이 도입되었으며, 그 중 가장 중요한 것은 제네릭, 익명 메서드, 반복자 및 불완전 유형(부분 유형)입니다.
제네릭을 사용하면 저장하고 조작하는 데이터 유형에 따라 클래스, 구조, 인터페이스, 대리자 및 메서드를 매개 변수화할 수 있습니다. 제네릭은 더 강력한 컴파일 타임 유형 검사를 제공하고 데이터 유형 간의 명시적 변환을 줄이며 박싱 작업 및 런타임 유형 검사를 제공하므로 유용합니다.
익명 메서드를 사용하면 코드 블록이 대리자 값이 예상되는 인라인으로 몰래 들어갈 수 있습니다. 익명 메서드는 Lisp 프로그래밍 언어의 람다 함수와 유사합니다. C# 2.0은 익명 메서드가 관련 지역 변수 및 매개 변수에 액세스할 수 있는 "클로저" 생성을 지원합니다.
Iterator는 점진적으로 값을 계산하고 생성할 수 있는 메서드입니다. 반복자를 사용하면 foreach 문이 해당 요소 전체를 반복하는 방법을 형식에서 쉽게 지정할 수 있습니다.
불완전한 유형을 사용하면 클래스, 구조 및 인터페이스를 여러 부분으로 분할하고 다른 소스 파일에 저장할 수 있으므로 개발 및 유지 관리에 더 도움이 됩니다. 또한 불완전한 유형을 사용하면 특정 유형의 기계 생성 부분과 사용자 작성 부분을 분리할 수 있으므로 도구로 생성된 코드를 쉽게 확장할 수 있습니다.
이 장에서는 이러한 새로운 기능을 소개합니다. 이 소개 이후 다음 4개 장에서는 이러한 기능에 대한 완전한 기술 사양을 제공합니다.
C# 2.0 언어 확장은 기본적으로 기존 코드와의 호환성을 최대화하도록 설계되었습니다. 예를 들어 C# 2.0에서는 특정 컨텍스트에서 where, 항복 및 부분이라는 단어에 특별한 의미를 할당하지만 이러한 단어는 여전히 식별자로 사용될 수 있습니다. 실제로 C# 2.0에서는 기존 코드의 식별자와 충돌할 수 있는 키워드를 추가하지 않습니다.
19.1 제네릭
제네릭을 사용하면 저장하고 조작하는 데이터 유형에 따라 클래스, 구조, 인터페이스, 대리자 및 메서드를 매개 변수화할 수 있습니다. C# 제네릭은 에펠 또는 Ada의 제네릭 사용자나 C++ 템플릿 사용자에게 친숙하지만 더 이상 후자의 많은 복잡성을 견딜 필요가 없습니다.
19.1.1 제네릭을 사용하는 이유
제네릭이 없으면 범용 데이터 구조에서는 객체 유형을 사용하여 모든 유형의 데이터를 저장할 수 있습니다. 예를 들어, 다음 Stack 클래스는 데이터를 개체 배열에 저장하고 해당 클래스의 두 가지 메서드인 Push 및 Pop은 개체를 사용하여 그에 따라 데이터를 수신하고 반환합니다.
public class Stack { object[] items; int count; public void Push(object item){…} public object Pop(){…} }
형 객체를 사용하면 Stack 클래스를 더 유연하게 만들 수 있지만 단점이 없는 것은 아닙니다. 예를 들어 Customer 인스턴스와 같은 모든 유형의 값을 스택에 푸시할 수 있습니다. 그러나 값을 다시 얻을 때 Pop 메서드의 결과는 명시적으로 적절한 유형으로 캐스팅되어야 하며 런타임 유형 확인을 위한 코드를 작성하는 것은 성가신 일이며 그에 따른 성능 저하가 발생합니다.
Stack stack = new Stack(); Stack.Push(new Customer()); Customer c = (Customer)stack.Pop();
int와 같은 값 유형이 Push 메서드에 전달되면 자동으로 boxing됩니다. 나중에 int를 얻으면 명시적 캐스트를 사용하여 unboxing해야 합니다.
Stack stack = new Stack(); Stack.Push(3); int I = (int)stack.Pop();
이러한 박싱 및 언박싱 작업에는 동적 메모리 할당 및 런타임 유형 검사가 포함되므로 성능 오버헤드가 추가됩니다.
Stack 클래스의 더 큰 문제는 스택에 배치된 데이터 종류를 강제할 수 없다는 것입니다. 실제로 Customer 인스턴스는 스택에 푸시되어 검색 시 잘못된 유형으로 캐스팅될 수 있습니다.
Stack stack = new Stack(); Stack.Push(new Customer()); String s = (string)stack.Pop();
이전 코드는 Stack 클래스를 부적절하게 사용했지만 이 코드는 기술적으로 정확하며 컴파일 시간 오류를 보고하지 않습니다. 코드가 실행될 때까지 문제가 발생하지 않으며, 이 시점에서 InvalidCastException이 발생합니다.
Stack 클래스에 해당 요소의 유형을 지정하는 기능이 있다면 분명히 이 기능의 이점을 누릴 것입니다. 제네릭을 사용하면 이것이 가능해집니다.
19.1.2 제네릭 생성 및 사용
제네릭은 유형 매개변수를 사용하여 유형을 생성하기 위한 도구를 제공합니다. 다음 예제에서는 형식 매개 변수 T를 사용하여 일반 Stack 클래스를 선언합니다. 유형 매개변수는 클래스 이름 뒤의 "<" 및 ">" 구분 기호에 지정됩니다. 객체와 다른 유형 사이에는 변환이 없습니다. Stack
Public class Stack<T> { T[] items; int count; public void Push(T item){…} public T Pop(){…} }
当泛型类Stack
Stack<int> stack = new Stack<int>(); Stack.Push(3); int x = stack.Pop();
Stack
泛型提供了强类型,意义例如压入一个int到Customer对象堆栈将会出现错误。就好像Stack
对于下面的例子,编译器将会在最后两行报告错误。
Stack<Customer> stack = new Stack<Customer>(); Stack.Push(new Customer()); Customer c = stack.Pop(); stack.Push(3); //类型不匹配错误 int x = stack.Pop(); //类型不匹配错误
泛型类型声明可以有任意数量的类型参数。先前的Stack
public class Dictionary<K , V> { public void Add(K key , V value){…} public V this[K key]{…} } 当Dictionary<K , V> 被使用时,必须提供两个类型参数。 Dictionary<string , Customer> dict = new Dictionary<string , Customer>(); Dict.Add(“Peter”, new Customer()); Custeomer c = dict[“Perter”];
19.1.3泛型类型实例化
与非泛型类型相似,被编译过的泛型类型也是由中间语言[Intermediate Language(IL)]指令和元数据表示。泛型类型的表示当然也对类型参数的存在和使用进行了编码。
当应用程序首次创建一个构造泛型类型的实例时,例如,Stack
.NET公共语言运行时使用值类型为每个泛型类型实例创建了一个本地代码的特定拷贝,但对于所有的引用类型它将共享那份本地代码的单一拷贝(因为,在本地代码级别,引用只是带有相同表示的指针)。
19.1.4约束
一般来讲,泛型类不限于只是根据类型参数存储值。泛型类经常可能在给定类型参数的类型的对象上调用方法。例如,Dictionary
public class Dictionary<K , V> { public void Add(K key , V value) { … if(key.CompareTo(x)<0){…}//错误,没有CompareTo方法 … } }
因为为K所指定的类型参数可能是任何类型,可以假定key参数存在的唯一成员,就是那些被声明为object类型的,例如,Equals,GetHashCode和ToString;因此,在先前例子中将会出现编译时错误。当然,你可以将key参数强制转换到一个包含CompareTo方法的类型。例如,key参数可能被强制转换到IComparable接口。
public class Dictionary<K , V> { public void Add(K key , V value) { … if(((IComparable)key).CompareTo(x)<0){…} … } }
尽管这种解决办法有效,但它需要在运行时的动态类型检查,这也增加了开销。更糟糕的是,它将错误报告推迟到了运行时,如果键(key)没有实现IComparable接口将会抛出InvalidCastException异常。
为了提供更强的编译时类型检查,并减少类型强制转换,C#允许为每个类型参数提供一个约束(constraint)的可选的列表。类型参数约束指定了类型必须履行的一种需求,其目的是为了为类型参数被用作实参(argument)。约束使用单词where声明,随后是类型参数的名字,接着是类或接口类型的列表,和可选的构造函数约束new()。
public class Dictionary<K, V> where K :IComparable { public void Add(K key , V value) { … if(key.CompareTo(x)<0){…} … } }
给定这个声明,编译器将会确保K的任何类型实参是实现了IComparable接口的类型。
并且,在调用CompareTo方法之前也不再需要对key参数进行显式地强制转换。为类型参数作为一个约束而给出的类型的所有成员,对于类型参数类型的值时直接有效的。
对于一个给定的类型参数,你可以指定任意数量的接口作为约束,但只能有一个类。每个约束的类型参数有一个单独的where 语句。在下面的例子中,类型参数K有两个接口约束,类型参数e有一个类约束和一个构造函数约束。
public class EntityTable<K, E> where K:IComparable<K>,IPersisable where E:Entity, new() { public void Add(K key , E entity) { … if(key.CompareTo(x)<0){…} … } }
在前面的例子中,构造函数约束new(),确保为E用作类型参数的类型具有一个公有的、无参数构造函数,并且它允许泛型类使用new E()创建该类型的实例。
类型参数约束应该很小心的使用。尽管它们提供了更强的编译时类型检查,在某些情况下增强了性能,但它们也限制了泛型类型的可能的用法。例如,泛型类List
以上就是C# 2.0 Specification(一)简介的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!