19. C# 2.0 の概要
C# 2.0 では、いくつかの言語拡張機能が導入されており、その中で最も重要なものは、ジェネリックス、匿名メソッド、イテレーター、および不完全型 (部分型) です。
ジェネリックを使用すると、クラス、構造体、インターフェイス、デリゲート、メソッドを、保存および操作するデータの種類によってパラメーター化できます。ジェネリックは、より強力なコンパイル時の型チェックを提供し、データ型間の明示的な変換だけでなく、ボックス化操作や実行時の型チェックも削減するため便利です。
匿名メソッドを使用すると、デリゲート値が予期される場所にコード ブロックをインラインでこっそり入れることができます。匿名メソッドは、Lisp プログラミング言語のラムダ関数に似ています。 C# 2.0 は、匿名メソッドが関連するローカル変数およびパラメーターにアクセスできる「クロージャ」の作成をサポートしています。
イテレーターは、値を段階的に計算して生成できるメソッドです。イテレータを使用すると、foreach ステートメントがすべての要素に対して反復処理を行う方法を型で簡単に指定できます。
不完全型を使用すると、クラス、構造、インターフェイスを複数の部分に分割し、異なるソース ファイルに保存できるため、開発とメンテナンスが容易になります。さらに、不完全型を使用すると、特定の型の機械生成部分とユーザー作成部分を分離できるため、ツールによって生成されたコードを簡単に拡張できます。
この章では、これらの新機能を紹介します。この概要の後に、次の 4 章でこれらの機能の完全な技術仕様を説明します。
C# 2.0 言語拡張機能は主に、既存のコードとの互換性を最大限に確保するように設計されています。たとえば、C# 2.0 では、特定のコンテキストで where、yield、partial という単語に特別な意味が割り当てられますが、これらの単語は引き続き識別子として使用できます。実際、C# 2.0 では、既存のコード内の識別子と競合する可能性のあるキーワードは追加されません。
19.1 ジェネリック
ジェネリックを使用すると、クラス、構造体、インターフェイス、デリゲート、メソッドを、保存および操作するデータの種類によってパラメータ化できます。 C# ジェネリックは、Eiffel や Ada のジェネリックのユーザー、または C++ テンプレートのユーザーにとって馴染みのあるものになりますが、後者の多くの複雑さに耐える必要はなくなります。
19.1.1 ジェネリックスを使用する理由
ジェネリックスを使用しない場合、汎用データ構造はオブジェクト型を使用してあらゆるタイプのデータを格納できます。たとえば、次の Stack クラスはデータをオブジェクト配列に格納し、その 2 つのメソッド、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 メソッドに渡すと、自動的にボックス化されます。後で int を取得する場合は、明示的なキャストを使用してボックス化を解除する必要があります。
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中文网(www.php.cn)!