C# 2.0 仕様の概要 (1)

黄舟
リリース: 2017-01-03 10:10:01
オリジナル
1449 人が閲覧しました

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 のインスタンスは、作成された型を受け入れ、その型のデータを変換せずに保存します。型パラメーター T はプレースホルダーとして機能し、使用されるまで実際の型を指定しません。なお、Tは内部項目配列の要素型、Pushメソッドのパラメータの型、Popメソッドの戻り値の型として使用されます。

Public class Stack<T>
{
T[] items;
int count;
public void Push(T item){…}
public T Pop(){…}
}
ログイン後にコピー

当泛型类Stack被使用时,T所代替的实际类型将被指定。在下面的例子中,int 将被作为T的类型参数而给出。

Stack<int> stack = new Stack<int>();
Stack.Push(3);
int x = stack.Pop();
ログイン後にコピー

Stack类型被称为构造类型(constructed type)。在Stack类型中,T的每次出现都被使用类型参数int代替。当Stack的实例被创建时,items数组的本地存储就是一个int[]而不是object[],与非泛型Stack相比,它提供了更高的存储效率。同样地,在int值上的Stack操作的Push和Pop方法,将会使得压入其他类型的值到堆栈中出现一个编译时错误,并且当取回值的时候也不需要转换回它们原始的类型。

泛型提供了强类型,意义例如压入一个int到Customer对象堆栈将会出现错误。就好像Stack被限制只能在int值上操作,同样Stack也被限制用于Customer对象。

对于下面的例子,编译器将会在最后两行报告错误。

Stack<Customer> stack = new Stack<Customer>();
Stack.Push(new Customer());
Customer c = stack.Pop();
stack.Push(3); //类型不匹配错误
int x = stack.Pop(); //类型不匹配错误
ログイン後にコピー

泛型类型声明可以有任意数量的类型参数。先前的Stack例子 只有一个类型参数,但一个通用的Dictionary类可能有两个类型参数,一个用于键(key)的类型,另一个用于值(value)的类型。

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公共语言运行时的实时编译器(JIT)将在进程中把泛型IL和元数据转换为本地代码,并且将类型参数替换为实际的类型。对于那个构造泛型类型的后续引用将会使用相同的本机代码。从一个泛型类型创建一个特定构造类型的过程,称为泛型类型实例化(generic type instantiation)。[/b]

.NET公共语言运行时使用值类型为每个泛型类型实例创建了一个本地代码的特定拷贝,但对于所有的引用类型它将共享那份本地代码的单一拷贝(因为,在本地代码级别,引用只是带有相同表示的指针)。

19.1.4约束

一般来讲,泛型类不限于只是根据类型参数存储值。泛型类经常可能在给定类型参数的类型的对象上调用方法。例如,Dictionary类中的Add方法可能需要使用CompareTo方法比较键值。

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可能约束T实现IComparable接口,由此它的Sort方法将可以比较项的大小。然而,这么做却使得没有实现IComparable 接口的类型不能使用List,即使是在这些情形下,Sort方法根本就没有被调用过。

以上就是C# 2.0 Specification(一)简介的内容,更多相关内容请关注PHP中文网(www.php.cn)!


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート