19.C#2.0介紹
C#2.0引入了幾項語言擴展,其中最重要的是泛型、匿名方法、迭代器和不完整類型(partial type)。
泛型可以讓類別、結構、介面、委託和方法,透過他們所儲存和操縱的資料的類型被參數化。泛型是很有用的,因為他們提供了更強的編譯時類型檢查,減少了資料類型之間的明確轉換,以及裝箱操作和運行時類型檢查。
匿名方法可以讓程式碼區塊以內聯的方式潛入期望委託值的地方。匿名方法與Lisp 程式語言中的λ函數(lambda function)相似。 C#2.0支援「closures」的創建,在其中匿名方法可以存取相關局部變數和參數。
迭代器是可以遞增計算和產生值的方法。迭代器讓類型指定foreach語句如何迭代它的所有元素,變得很容易。
不完整類型可以讓類別、結構和介面被分割成多個部分儲存在不同的來源檔案中,這更有利於開發和維護。此外,不完整類型允許某些類型的機器產生的部分與使用者編寫的部分之間的分離,因此增加由工具產生的程式碼很容易。
本章將介紹這些新特徵。介紹完之後,接下來的四章提供了這些特徵的完整的技術規格。
C#2.0的語言擴充主要被設計用來確保與現存的程式碼之間最大的相容性。例如,儘管C#2.0對於where、yield 和partial這些詞在特定上下文中賦予了特別的意義,但這些詞仍然可被用作標識符。實際上,C# 2.0並沒有增加任何可能與現有程式碼中的識別碼衝突的關鍵字。
19.1 泛型
泛型可以讓類別、結構、介面、委託和方法,透過他們所儲存和操縱的資料的類型被參數化。 C#泛型對於使用Eiffel或Ada的泛型的用戶,或者對於C++模板的用戶來說是很熟悉的;但他們將不用再去忍受後者的眾多的複雜性。
19.1.1為什麼使用泛型
沒有泛型的話,通用目的的資料結構可以採用object類型儲存任何類型的資料。例如,下面的Stack類別在一個object數組中儲存數據,而它的兩個方法,Push和Pop相應地使用object接收和返回數據。
public class Stack { object[] items; int count; public void Push(object item){…} public object Pop(){…} }
儘管使用型object可以讓Stack類別更有彈性,但這樣做並不是沒有缺點。例如,你可以將一個任何類型的值,諸如,Customer的一個實例壓入(Push)堆疊。但當你取回一個值時,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 建立和使用泛型
泛型為建立具有型別參數(type parameter)的型別提供了工具。下面的範例聲明了一個帶有型別參數T的泛型Stack類別。類型參數在類別名字之後的“”分界符中指定。這裡沒有object與別的類型之間的相互轉換,Stack
Public class Stack<T> { T[] items; int count; public void Push(T item){…} public T Pop(){…} }
当泛型类Stack8742468051c85b06f0a0af9e3e506b5c被使用时,T所代替的实际类型将被指定。在下面的例子中,int 将被作为T的类型参数而给出。
Stack<int> stack = new Stack<int>(); Stack.Push(3); int x = stack.Pop();
Stackbd43222e33876353aff11e13a7dc75f6类型被称为构造类型(constructed type)。在Stackbd43222e33876353aff11e13a7dc75f6类型中,T的每次出现都被使用类型参数int代替。当Stackbd43222e33876353aff11e13a7dc75f6的实例被创建时,items数组的本地存储就是一个int[]而不是object[],与非泛型Stack相比,它提供了更高的存储效率。同样地,在int值上的Stackbd43222e33876353aff11e13a7dc75f6操作的Push和Pop方法,将会使得压入其他类型的值到堆栈中出现一个编译时错误,并且当取回值的时候也不需要转换回它们原始的类型。
泛型提供了强类型,意义例如压入一个int到Customer对象堆栈将会出现错误。就好像Stackbd43222e33876353aff11e13a7dc75f6被限制只能在int值上操作,同样Stackc214b1127c801bd6a2a45c5b466f54b2也被限制用于Customer对象。
对于下面的例子,编译器将会在最后两行报告错误。
Stack<Customer> stack = new Stack<Customer>(); Stack.Push(new Customer()); Customer c = stack.Pop(); stack.Push(3); //类型不匹配错误 int x = stack.Pop(); //类型不匹配错误
泛型类型声明可以有任意数量的类型参数。先前的Stack8742468051c85b06f0a0af9e3e506b5c例子 只有一个类型参数,但一个通用的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)]指令和元数据表示。泛型类型的表示当然也对类型参数的存在和使用进行了编码。
当应用程序首次创建一个构造泛型类型的实例时,例如,Stackbd43222e33876353aff11e13a7dc75f6,.NET公共语言运行时的实时编译器(JIT)将在进程中把泛型IL和元数据转换为本地代码,并且将类型参数替换为实际的类型。对于那个构造泛型类型的后续引用将会使用相同的本机代码。从一个泛型类型创建一个特定构造类型的过程,称为泛型类型实例化(generic type instantiation)。[/b]
.NET公共语言运行时使用值类型为每个泛型类型实例创建了一个本地代码的特定拷贝,但对于所有的引用类型它将共享那份本地代码的单一拷贝(因为,在本地代码级别,引用只是带有相同表示的指针)。
19.1.4约束
一般来讲,泛型类不限于只是根据类型参数存储值。泛型类经常可能在给定类型参数的类型的对象上调用方法。例如,Dictionarybfbf51059cfb0a0d14f2e5a287507931类中的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()创建该类型的实例。
类型参数约束应该很小心的使用。尽管它们提供了更强的编译时类型检查,在某些情况下增强了性能,但它们也限制了泛型类型的可能的用法。例如,泛型类List8742468051c85b06f0a0af9e3e506b5c可能约束T实现IComparable接口,由此它的Sort方法将可以比较项的大小。然而,这么做却使得没有实现IComparable 接口的类型不能使用List8742468051c85b06f0a0af9e3e506b5c,即使是在这些情形下,Sort方法根本就没有被调用过。
以上就是C# 2.0 Specification(一)简介的内容,更多相关内容请关注PHP中文网(www.php.cn)!