第一章 C#2.0简介
C# 2.0引入了很多语言扩展,最重要的就是泛型(Generics)、匿名方法(Anonymous Methods)、迭代器
(Iterators)和不完全类型(Partial Types)。
● 泛型允许类、结构、接口、委托和方法通过它们所存贮和操作的数据的类型来参数化。泛型是很有
用的,因为它提供了更为强大的编译期间类型检查,需要更少的数据类型之间的显式转换,并且减少了
对装箱操作的需要和运行时的类型检查。
● 匿名方法允许在需要委托值时能够以“内联(in-line)”的方式书写代码块。匿名方法与Lisp语言
中的拉姆达函数(lambda functions)类似。
● 迭代器是能够增量地计算和产生一系列值得方法。迭代器使得一个类能够很容易地解释foreach语句
将如何迭代他的每一个元素。
● 不完全类型允许类、结构和接口被分成多个小块儿并存贮在不同的源文件中使其容易开发和维护。
另外,不完全类型可以分离机器产生的代码和用户书写的部分,这使得用工具来加强产生的代码变得容
易。
这一章首先对这些新特性做一个简介。简介之后有四章,提供了这些特性的完整的技术规范。
C# 2.0中的语言扩展的设计可以保证和现有代码的高度的兼容性。例如,尽管C#2.0在特定的环境中对
单词where、yield和partial赋予了特殊的意义,这些单词还是可以被用作标识符。确实,C# 2.0没有增加一
个会和现有代码中的标识符冲突的关键字。
1.1 泛型
泛型允许类、结构、接口、委托和方法通过它们所存贮和操作的数据的类型来参数化。C#泛型对使
用Eiffel或Ada语言泛型的用户和使用C++模板的用户来说相当亲切,尽管它们也许无法忍受后者的复杂
性。
1.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实例。然而,重新取回一个值得时候,必须将Pop方法返回的值显式地转换为合
适的类型,书写这些转换变更要提防运行时类型检查错误是很乏味的:
Stack stack = new Stack();
stack.Push(new Customer());
Customer c = (Customer)stack.Pop();
如果一个值类型的值,如int,传递给了Push方法,它会自动装箱。而当待会儿取回这个int值时,必
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 1/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
须显式的类型转换进行拆箱:
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类无疑会从具有限定其元素类型的能力中获益。使用泛型,这将成为可能。
1.1.2 建立和使用泛型
泛型提供了一个技巧来建立带有类型参数(type parameters)的类型。下面的例子声明了一个带有类
型参数T的泛型Stack类。类型参数又类名字后面的定界符“<”和“>”指定。通过某种类型建立的
Stack<T>的实例 可以无欲转换地接受该种类型的数据,这强过于与object相互装换。类型参数T扮演一个
占位符的角色,直到使用时指定了一个实际的类型。注意T相当于内部数组的数据类型、Push方法接受的
参数类型和Pop方法的返回值类型:
public class Stack<T>
{
T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}
使用泛型类Stack<T>时,需要指定实际的类型来替代T。下面的例子中,指定int作为参数类型T:
Stack<int> stack = new Stack<int>();
stack.Push(3);
int x = stack.Pop();
Stack<int>类型称为已构造类型(constructed type)。在Stack<int>类型中出现的所有T被替换为类型参
数int。当一个Stack<int>的实例被创建时,items数组的本地存贮是int[]而不是object[],这提供了一个实质
的存贮,效率要高过非泛型的Stack。同样,Stack<int>中的Push和Pop方法只操作int值,如果向堆栈中压
入其他类型的值将会得到编译期间的错误,而且取回一个值时不必将它显示转换为原类型。
泛型可以提供强类型,这意味着例如向一个Customer对象的堆栈上压入一个int将会产生错误。这是因
为Stack<int>只能操作int值,而Stack<Customer>也只能操作Customer对象。下面例子中的最后两行会导致
编译器报错:
Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
Customer c = stack.Pop();
stack.Push(3); // 类型不匹配错误
int x = stack.Pop(); // 类型不匹配错误
泛型类型的声明允许任意数目的类型参数。上面的Stack<T>例子只有一个类型参数,但一个泛型的
Dictionary类可能有两个类型参数,一个是键的类型另一个是值的类型:
public class Dictionary<K,V>
{
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 2/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
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());
Customer c = dict["Peter"];
1.1.3 泛型类型实例化
和非泛型类型类似,编译过的泛型类型也由中间语言(IL, Intermediate Language)指令和元数据表
示。泛型类型的IL表示当然已由类型参数进行了编码。
当程序第一次建立一个已构造的泛型类型的实例时,如Stack<int>,.NET公共语言运行时中的即时编
译器(JIT, just-in-time)将泛型IL和元数据转换为本地代码,并在进程中用实际类型代替类型参数。后面
的对这个以构造的泛型类型的引用使用相同的本地代码。从泛型类型建立一个特定的构造类型的过程称
为泛型类型实例化(generic type instantiation)。
.NET公共语言运行时为每个由之类型实例化的泛型类型建立一个专门的拷贝,而所有的引用类型共
享一个单独的拷贝(因为,在本地代码级别上,引用知识具有相同表现的指针)。
1.1.4 约束
通常,一个泛型类不会只是存贮基于某一类型参数的数据,他还会调用给定类型的对象的方法。例
如,Dictionary<K,V>中的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) {...}
...
}
}
当这种方案工作时,会在运行时引起动态类型转换,会增加开销。更要命的是,它还可能将错误报
告推迟到运行时。如果一个键没有实现IComparable接口,会抛出InvalidCastException异常。
为了提供更强大的编译期间类型检查和减少类型转换,C#允许一个可选的为每个类型参数提供的约
束(constraints)列表。一个类型参数的约束指定了一个类型必须遵守的要求,使得这个类型参数能够作
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 3/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
为一个变量来使用。约束由关键字where来声明,后跟类型参数的名字,再后是一个类或接口类型的列
表,或构造器约束new()。
要想使Dictionary<K,V>类能保证键值始终实现了IComparable接口,类的声明中应该对类型参数K指定
一个约束:
public class Dictionary<K,V> where K: IComparable
{
public void Add(K key, V value)
{
...
if (key.CompareTo(x) < 0) {...}
...
}
}
通过这个声明,编译器能够保证所有提供给类型参数K的类型都实现了IComparable接口。进而,在调
用CompareTo方法前不再需要将键值显式转换为一个IComparable接口;一个受约束的类型参数类型的值
的所有成员都可以直接使用。
对于给定的类型参数,可以指定任意数目的接口作为约束,但只能指定一个类(作为约束)。每一
个被约束的类型参数都有一个独立的where子句。在下面的例子中,类型参数K有两个接口约束,而类型
参数E有一个类约束和一个构造器约束:
public class EntityTable<K,E>
where K: IComparable<K>, IPersistable
where E: Entity, new()
{
public void Add(K key, E entity)
{
...
if (key.CompareTo(x) < 0) {...}
...
}
}
上面例子中的构造器约束,new(),保证了作为的E类型变量的类型具有一个公共、无参的构造器,并
允许泛型类使用new E()来建立该类型的一个实例。
类型参数约束的使用要小心。尽管它们提供了更强大的编译期间类型检查并在一些情况下改进了性
能,它还是限制了泛型类型的使用。例如,一个泛型类List<T>可能约束T实现IComparable接口以便Sort方
法能够比较其中的元素。然而,这么做使List<T>不能用于那些没有实现IComparable接口的类型,尽管在
这种情况下Sort方法从来没被实际调用过。
1.1.5 泛型方法
有的时候一个类型参数并不是整个类所必需的,而只用于一个特定的方法中。通常,这种情况发生
在建立一个需要一个泛型类型作为参数的方法时。例如,在使用前面描述过的Stack<T>类时,一种公共
的模式就是在一行中压入多个值,如果写一个方法通过单独调用它类完成这一工作会很方便。对于一个
特定的构造过的类型,如Stack<int>,这个方法看起来会是这样:
void PushMultiple(Stack<int> stack, params int[] values) {
foreach (int value in values) stack.Push(value);
}
这个方法可以用于将多个int值压入一个Stack<int>:
Stack<int> stack = new Stack<int>();
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 4/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
PushMultiple(stack, 1, 2, 3, 4);
然而,上面的方法只能工作于特定的构造过的类型Stack<int>。要想使他工作于任何Stack<T>,这个
方法必须写成泛型方法(generic method)。一个泛型方法有一个或多个类型参数,有方法名后面
的“<”和“>”限定符指定。这个类型参数可以用在参数列表、返回至和方法体中。一个泛型的
PushMultiple方法看起来会是这样:
void PushMultiple<T>(Stack<T> stack, params T[] values) {
foreach (T value in values) stack.Push(value);
}
使用这个方法,可以将多个元素压入任何Stack<T>中。当调用一个泛型方法时,要在函数的调用中将
类型参数放入尖括号中。例如:
Stack<int> stack = new Stack<int>();
PushMultiple<int>(stack, 1, 2, 3, 4);
这个泛型的PushMultiple方法比上面的版本更具可重用性,因为它能工作于任何Stack<T>,但这看起
来并不舒服,因为必须为T提供一个类型参数。然而,很多时候编译器可以通过传递给方法的其他参数来
推断出正确的类型参数,这个过程称为类型推断(type inferencing)。在上面的例子中,由于第一个正式
的参数的类型是Stack<int>,并且后面的参数类型都是int,编译器可以认定类型参数一定是int。因此,在
调用泛型的PushMultiple方法时可以不用提供类型参数:
Stack<int> stack = new Stack<int>();
PushMultiple(stack, 1, 2, 3, 4);
1.2 匿名方法
实践处理方法和其他回调方法通常需要通过专门的委托来调用,而不是直接调用。因此,迄今为止
我们还只能将一个实践处理和回调的代码放在一个具体的方法中,再为其显式地建立委托。相反,匿名
方法(anonymous methods)允许将与一个委托关联的代码“内联(in-line)”到使用委托的地方,我们可
以很方便地将代码直接写在委托实例中。除了看起来舒服,匿名方法还共享对本地语句所包含的函数成
员的访问。如果想在命名方法(区别于匿名方法)中达成这种共享,需要手动创建一个辅助类并将本地
成员“提升(lifting)”到这个类的域中。
下面的例子展示了从一个包含一个列表框、一个文本框和一个按钮的窗体中获取一个简单的输入。
当按钮按下时文本框中的文本会被添加到列表框中。
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += new EventHandler(AddClick);
}
void AddClick(object sender, EventArgs e) {
listBox.Items.Add(textBox.Text);
}
}
尽管对按钮的Click事件的响应只有一条语句,这条语句也必须放到一个独立的具有完整的参数列表
的方法中,并且要手动创建引用该方法的EventHandler委托。使用匿名方法,事件处理的代码会变得更加
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 5/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
简洁:
class InputForm: Form
{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += delegate {
listBox.Items.Add(textBox.Text);
};
}
}
一个匿名方法由关键字delegate和一个可选的参数列表组成,并将语句放入“{”和“}”限定符中。前
面例子中的匿名方法没有使用提供给委托的参数,因此可以省略参数列表。要想访问参数,你名方法应
该包含一个参数列表:
addButton.Click += delegate(object sender, EventArgs e) {
MessageBox.Show(((Button)sender).Text);
};
上面的例子中,在匿名方法和EventHandler委托类型(Click事件的类型)之间发生了一个隐式的转
换。这个隐式的转换是可行的,因为这个委托的参数列表和返回值类型和匿名方法是兼容的。精确的兼
容规则如下:
? 当下面条例中有一条为真时,则委托的参数列表和匿名方法是兼容的:
o 匿名方法没有参数列表且委托没有输出(out)参数。
o 匿名方法的参数列表在参数数目、类型和修饰符上与委托参数精确匹配。
? 当下面的条例中有一条为真时,委托的返回值与匿名方法兼容:
o 委托的返回值类型是void且匿名方法没有return语句或其return语句不带任何表达式。
o 委托的返回值类型不是void但和匿名方法的return语句关联的表达式的值可以被显式地转换为委
托的返回值类型。
只有参数列表和返回值类型都兼容的时候,才会发生匿名类型向委托类型的隐式转换。
下面的例子使用了匿名方法对函数进行了“内联(in-lian)”。匿名方法被作为一个Function委托类
型传递。
using System;
delegate double Function(double x);
class Test
{
static double[] Apply(double[] a, Function f) {
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}
static double[] MultiplyAllBy(double[] a, double factor) {
return Apply(a, delegate(double x) { return x * factor; });
}
static void Main() {
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 6/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
double[] a = {0.0, 0.5, 1.0};
double[] squares = Apply(a, delegate(double x) { return x * x; });
double[] doubles = MultiplyAllBy(a, 2.0);
}
}
Apply方法需要一个给定的接受double[]元素并返回double[]作为结果的Function。在Main方法中,传
递给Apply方法的第二个参数是一个匿名方法,它与Function委托类型是兼容的。这个匿名方法只简单地
返回每个元素的平方值,因此调用Apply方法得到的double[]包含了a中每个值的平方值。
MultiplyAllBy方法通过将参数数组中的每一个值乘以一个给定的factor来建立一个double[]并返回。为
了产生这个结果,MultiplyAllBy方法调用了Apply方法,向它传递了一个能够将参数x与factor相乘的匿名方
法。
如果一个本地变量或参数的作用域包括了匿名方法,则该变量或参数称为匿名方法的外部变量
(outer variables)。在MultiplyAllBy方法中,a和factor就是传递给Apply方法的匿名方法的外部变量。通
常,一个局部变量的生存期被限制在块内或与之相关联的语句内。然而,一个被捕获的外部变量的生存
期要扩展到至少对匿名方法的委托引用符合垃圾收集条件时。
1.2.1 方法组转换
像前面章节中描述过的那样,一个匿名方法可以被隐式转换为一个兼容的委托类型。C# 2.0允许对一
组方法进行相同的转换,即所任何时候都可以省略一个委托的显式实例化。例如,下面的语句:
addButton.Click += new EventHandler(AddClick);
Apply(a, new Function(Math.Sin));
还可以写做:
addButton.Click += AddClick;
Apply(a, Math.Sin);
当使用短形式时,编译器可以自动地推断应该实例化哪一个委托类型,不过除此之外的效果都和长形式
相同。
1.3 迭代器
C#中的foreach语句用于迭代一个可枚举(enumerable)的集合中的元素。为了实现可枚举,一个集合
必须要有一个无参的、返回枚举器(enumerator)的GetEnumerator方法。通常,枚举器是很难实现的,因
此简化枚举器的任务意义重大。
迭代器(iterator)是一块可以产生(yields)值的有序序列的语句块。迭代器通过出现的一个或多个
yield语句来区别于一般的语句块:
? yield return语句产生本次迭代的下一个值。
? yield break语句指出本次迭代完成。
只要一个函数成员的返回值是一个枚举器接口(enumerator interfaces)或一个可枚举接口
(enumerable interfaces),我们就可以使用迭代器:
? 所谓枚举器借口是指System.Collections.IEnumerator和从System.Collections.Generic.IEnumerator<T>构造
的类型。
? 所谓可枚举接口是指System.Collections.IEnumerable和从System.Collections.Generic.IEnumerable<T>构造
的类型。
理解迭代器并不是一种成员,而是实现一个功能成员是很重要的。一个通过迭代器实现的成员可以
用一个或使用或不使用迭代器的成员覆盖或重写。
下面的Stack<T>类使用迭代器实现了它的GetEnumerator方法。其中的迭代器按照从顶端到底端的顺
序枚举了栈中的元素。
using System.Collections.Generic;
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 7/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
public class Stack<T>: IEnumerable<T>
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator<T> GetEnumerator() {
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
}
GetEnumerator方法的出现使得Stack<T>成为一个可枚举类型,这允许Stack<T>的实例使用foreach语
句。下面的例子将值0至9压入一个整数堆栈,然后使用foreach循环按照从顶端到底端的顺序显示每一个
值。
using System;
class Test
{
static void Main() {
Stack<int> stack = new Stack<int>();
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack) Console.Write("{0} ", i);
Console.WriteLine();
}
}
这个例子的输出为:
9 8 7 6 5 4 3 2 1 0
语句隐式地调用了集合的无参的GetEnumerator方法来得到一个枚举器。一个集合类中只能定义一个
这样的无参的GetEnumerator方法,不过通常可以通过很多途径来实现枚举,包括使用参数来控制枚举。
在这些情况下,一个集合可以使用迭代器来实现能够返回可枚举接口的属性和方法。例如,Stack<T>可
以引入两个新的属性——IEnumerable<T>类型的TopToBottom和BottomToTop:
using System.Collections.Generic;
public class Stack<T>: IEnumerable<T>
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator<T> GetEnumerator() {
for (int i = count – 1; i >= 0; --i) {
yield return items[i];
}
}
public IEnumerable<T> TopToBottom {
get {
return this;
}
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 8/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
}
public IEnumerable<T> BottomToTop {
get {
for (int i = 0; i < count; i++) {
yield return items[i];
}
}
}
}
TopToBottom属性的get访问器只返回this,因为堆栈本身就是一个可枚举类型。BottomToTop属性使
用C#迭代器返回了一个可枚举接口。下面的例子显示了如何使用这两个属性来以任意顺序枚举栈中的元
素:
using System;
class Test
{
static void Main() {
Stack<int> stack = new Stack<int>();
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack.TopToBottom) Console.Write("{0} ", i);
Console.WriteLine();
foreach (int i in stack.BottomToTop) Console.Write("{0} ", i);
Console.WriteLine();
}
}
当然,这些属性还可以用在foreach语句的外面。下面的例子将调用属性的结果传递给一个独立的Print
方法。这个例子还展示了一个迭代器被用作一个带参的FromToBy方法的方法体:
using System;
using System.Collections.Generic;
class Test
{
static void Print(IEnumerable<int> collection) {
foreach (int i in collection) Console.Write("{0} ", i);
Console.WriteLine();
}
static IEnumerable<int> FromToBy(int from, int to, int by) {
for (int i = from; i <= to; i += by) {
yield return i;
}
}
static void Main() {
Stack<int> stack = new Stack<int>();
for (int i = 0; i < 10; i++) stack.Push(i);
Print(stack.TopToBottom);
Print(stack.BottomToTop);
Print(FromToBy(10, 20, 2));
}
}
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 9/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
这个例子的输出为:
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
10 12 14 16 18 20
泛型和非泛型的可枚举接口都只有一个单独的成员,一个无参的GetEnumerator方法,它返回一个枚
举器接口。一个可枚举接口很像一个枚举器工厂(enumerator factory)。每当调用了一个正确地实现了可
枚举接口的类的GetEnumerator方法时,都会产生一个独立的枚举器。
using System;
using System.Collections.Generic;
class Test
{
static IEnumerable<int> FromTo(int from, int to) {
while (from <= to) yield return from++;
}
static void Main() {
IEnumerable<int> e = FromTo(1, 10);
foreach (int x in e) {
foreach (int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}
上面的代码打印了一个从1到10的简单乘法表。注意FromTo方法只调用了一次用来产生可枚举接口
e。而e.GetEnumerator()被调用了多次(通过foreach语句)来产生多个相同的枚举器。这些枚举器都封装
了FromTo声明中指定的代码。注意,迭代其代码改变了from参数。不过,枚举器是独立的,因为对于
from参数和to参数,每个枚举器拥有它自己的一份拷贝。在实现可枚举类和枚举器类时,枚举器之间的过
渡状态(一个不稳定状态)是必须消除的众多细微瑕疵之一。C#中的迭代器的设计可以帮助消除这些问
题,并且可以用一种简单的本能的方式来实现健壮的可枚举类和枚举器类。
1.4 不完全类型
尽管在一个单独的文件中维护一个类型的所有代码是一项很好的编程实践,但有些时候,当一个类
变得非常大,这就成了一种不切实际的约束。而且,程序员经常使用代码生成器来生成一个应用程序的
初始结构,然后修改产生的代码。不幸的是,当以后需要再次发布原代码的时候,现存的修正会被重
写。
不完全类型允许类、结构和接口被分成多个小块儿并存贮在不同的源文件中使其容易开发和维护。
另外,不完全类型可以分离机器产生的代码和用户书写的部分,这使得用工具来加强产生的代码变得容
易。
要在多个部分中定义一个类型的时候,我们使用一个新的修饰符——partial。下面的例子在两个部分
中实现了一个不完全类。这两个部分可能在不同的源文件中,例如第一部分可能是机器通过数据库影射
工具产生的,而第二部分是手动创作的:
public partial class Customer
{
private int id;
private string name;
private string address;
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 10/11 页)2005-9-21 10:24:13
第一章 C#2.0简介
private List<Order> orders;
public Customer() {
...
}
}
public partial class Customer
{
public void SubmitOrder(Order order) {
orders.Add(order);
}
public bool HasOutstandingOrders() {
return orders.Count > 0;
}
}
当上面的两部分编译到一起时,产生的代码就好像这个类被写在一个单元中一样:
public class Customer
{
private int id;
private string name;
private string address;
private List<Order> orders;
public Customer() {
...
}
public void SubmitOrder(Order order) {
orders.Add(order);
}
public bool HasOutstandingOrders() {
return orders.Count > 0;
}
}
不完全类型的所有部分必须放到一起编译,才能在编译期间将它们合并。需要特别注意的是,不完
全类型并不允许扩展已编译的类型。
file:///E|/mybook/C#2.0/C#2.0新特性/1.htm(第 11/11 页)2005-9-21 10:24:13
第二章 泛型
第二章 泛型
20.泛型
20.1泛型类声明
泛型类声明是一个需要提供类型参数以形成实际类型的类的声明。
类声明可以有选择地定义类型参数。
class-declaration: (类声明)
attributesopt class-modifiersopt class identifieropt type-parameter-listopt class –baseopt type-parameterconstraints-
clauseopt class-body;opt (特性可选 类修饰符可选 类标识符可选 类型参数列表可选 基类可选
类型参数约束语句可选 类体; 可选 )
除非提供了类型参数列表,类声明可以不提供类型参数化约束语句。
提供了类型参数列表的类声明是一个泛型类声明。此外,任何嵌入到泛型类声明或泛型结构声明中的
类,自身是一个泛型类声明,因为必须提供包含类型的类型参数以创建构造类型(constructed type);
泛型类通过使用构造类型而被引用(§20.5)。给定泛型类声明
class List<T>{}
这是构造类型的一些例子,List<T>,List<int>和List<List<string>>。构造类型可以使用一个或多个参数,
例如List<T>被称为开放构造类型(open constructed type)。不使用类型参数的构造类型,例如List<int>被
称为封闭构造类型(closed constructed type)。
泛型类型不可以被“重载”;也就是说,和普通类型一样在一个作用域内,泛型类型必须被唯一地命
名。
class C{}
class C<V>{}//错误,C定义了两次
class C<U,V>{}//错误,C定义了两次
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 1/20 页)2005-9-21 10:24:15
第二章 泛型
然而在非限定类型名字查找(§20.9.3)中使用的类型查找规则和成员访问(§20.9.4),确实考虑到了类
型参数的个数。
20.1.1类型参数
类型参数可以在一个类声明上提供。每个类型参数是一个简单的标识符,它指示了用来创建一个构造类
型的类型参数的占位符。类型参数是在后面将要被提供的类型的形式占位符。相反,类型参数§20.5.1)
只是在构造类型被引用时,实际类型的一个替代。
type-parameter-list:(类型参数列表:)
<type-parameters> (<类型参数>)
type-parameters:(类型参数:)
type-parameter(类型参数)
type-parameters type-parameter(类型参数,类型参数)
type-parameter:(类型参数:)
attributesopt identifier(特性可选 标识符)
在类声明中的每个类型参数在类的声明空间(§3.3)定义了一个名字。由此,它不能和另一个类型参数
或在类中声明的成员有同样的名字。类型参数不能和类型自身有同样的名字。
在一个类中的类型参数的作用域(§3.7),包括基类 、 类型参数约束语句和类体。不像类的成员,它没
有扩展到派生类。在其作用域之内,类型参数可以被用作一个类型。
type(类型):
value-type(值类型)
reference-type(引用类型)
type-parameter(类型参数)
由于类型参数可以被许多不同的实际类型实参所实例化,类型参数与其他类型相比将略微有一些不同的
操作和限制。包括如下内容。
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 2/20 页)2005-9-21 10:24:15
第二章 泛型
类型参数不能用于直接声明一个基类型或者接口
对于在类型参数上的成员查找规则,如果约束存在,则依赖于应用到该类型参数的约束。更详细地说明
参看§20.7.4。
类型参数可行的转换依赖于应用到该类型参数上的约束(如果有的话)。详细地说明参看§20.7.4。
字面null不能被转换到由类型参数所给定的类型,除非类型参数是由一个类约束(§20.7.4)所约束。然
而可以使用一个默认值表达式(§20.8.1)代替。此外,由一个类型参数给定的类型的值可以使
用“==”和“!=”(§20.8.4)与null进行比较。
如果类型参数通过一个构造函数约束(constructor-constraint)(§20.7)而约束,new表达式只能用过一
个类型参数而被使用。
类型参数不能用于特性内的任何地方。
类型参数不能用于成员访问,或者表示一个静态成员或者嵌套类型的类型名字(§20.9.1、§20.9.4)。
在不安全代码中,类型参数不能被用作托管类型(§18.2)。
作为一种类型,类型参数纯粹只是一个编译时构件。在运行时,每个类型参数被绑定到运行时类型,它
是通过泛型类型声明所提供的类型实参所指定的。为此,在运行时,使用类型参数声明的变量类型是一
个封闭类型(closed type)(§20.5.2)。所有语句和表达式在运行时执行所使用的类型参数,都是由那个
参数作为类型实参而提供的实际类型。
20.1.2实例类型
每个类声明都有与之关联的构造类型,即实例类型(instance type)。对于一个泛型类声明,实例类型通过创
建一个来自于类型声明的构造类型(§20.4)而形成,它使用对应于类型参数的每一个类型实参。由于实
例化类型使用类型参数,在类型参数作用域内(类声明之内),它是唯一有效的。实例类型在类声明中
是this的类型。对于非泛型类,实例类型只是一个声明类型。下面展示了几个声明类,以及它们的实例类
型。
class A<T> //实例类型:A<T>
{
class B{} //实例类型:A<T>.B
class C<U>{} //实例类型:A<T>.C<U>
}
class D{} //实例类型:D
20.1.3基类规范
在类声明中指定的基类可以是一个构造类型(§20.5)。一个基类其自身不能是一个类型参数,但在其作
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 3/20 页)2005-9-21 10:24:15
第二章 泛型
用域内可以包含类型参数。
class Extend<V>: V{}//错误,类型参数被用作基类
泛型类声明不能使用System.Attribute作为直接或间接基类。
在一个类声明中指定的基接口可以是构造接口类型(§20.5)。基接口自身不能是类型参数,但在其作用
域内可以包含类型参数,下面的代码演示了如何实现和扩展构造类型。
class C<U,V>{}
Interface I1<V>{}
class D:C<string , int>,I1<string>{}
class E<T>:C<int,T> ,I1<T>{}
泛型类型声明的基接口必须满足§20.3.1中所描述的唯一性规则。
从基类或接口重写或实现方法的类的方法,必须提供特定类型的合适方法。下面的代码演示了方法如何
被重写和实现。这将会在§20.1.10中进一步解释。
class C<U,V>
{
public virtual void M1(U x , List<V> y){⋯}
}
interface I1<V>
{
V M2(V x);
}
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 4/20 页)2005-9-21 10:24:15
第二章 泛型
class D:C<string , int>,I1<string>
{
public override void M1(string x , List<int> y){⋯}
public string M2(string x){⋯}
}
20.1.4泛型类的成员
泛型类的所有成员都可以直接地或者作为构造类型的一部分,从任何封闭类(enclosing class)中使用类型
参数。当特定的封闭构造类型在运行时被使用时,类型参数的每次使用都由构造类型所提供的实际类型
实参所代替。例如
class C<V>
{
public V f1;
public C<V> f2=null;
public C(V x){
this.f1 = x;
this.f2 = this;
}
}
class Application
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 5/20 页)2005-9-21 10:24:15
第二章 泛型
{
static void Main(){
C<int> x1= new C<int >(1);
Console.WriteLine(x1.f1); //打印1
C<double> x2 = new C<double>(3.1415);
Console.WriteLine(x2.f1); //打印 3.1415
}
}
在实例函数成员之内,this的类型就是声明的实例类型(§20.1.2)。
除了使用类型参数作为类型和成员,在泛型类声明中也遵循和非泛型类成员相同的规则。适用于特定种
类成员的附加规则将在后面几节进行讨论。
20.1.5泛型类中的静态字段
在一个泛型类声明中的静态变量,在相同封闭构造类型(§20.5.2)所有实例中被共享,但在不同封闭构
造类型的实例中[1],是不被共享的。这些规则不管静态变量的类型包含那种类型参数都适用。
例如
class C<V>
{
static int count = 0;
public C()
{
count++;
}
public static int Count{
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 6/20 页)2005-9-21 10:24:15
第二章 泛型
get{return count;}
}
}
class Application
{
static void Main()
{
C<int> x1 = new C<int>();
Console.WriteLine(C<int>.Count);//打印 1
C<double> x2 = new C<double>();
Console.WriteLine(C<int>.Count);//打印 1
C<int> x3 = new C<int>();
Console.WriteLine(C<int>.Count);//打印 2
}
}
--------------------------------------------------------------------------------
[1] 这是很容易理解的,因为在运行时,不同的封闭构造类型,是属于不同的类型,比如List<int> 和
List<string> 这二者的实例是不能共享静态变量的。
20.1.6泛型类中的静态构造函数
在泛型类中的静态构造函数被用于初始化静态字段,为每个从特定泛型类声明中创建的不同的封闭构造
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 7/20 页)2005-9-21 10:24:15
第二章 泛型
类型,执行其他初始化。泛型类型声明的类型参数在作用域之内,可以在静态构造函数体内被使用。
如果下列情形之一发生,一个新的封闭构造类类型将被首次初始化。
一个封闭构造类型的实例被创建时
封闭构造类型的任何静态成员被引用时
为了初始化一个新的封闭的构造类类型,首先那个特定封闭类型的一组新静态字段(§20.1.5)将会被创
建。每个静态字段都被初始化为其默认值(§5.2)。接着,静态字段初始化器(§10.4.5.1)将为这些静
态字段执行。最后静态构造函数将被执行。
由于静态构造函数将为每个封闭构造类类型执行一次,那么在不能通过约束(§20.7)检查的类型参数上
实施运行时检查,将会很方便。例如,下面的类型使用一个静态构造函数检查一个类型参数是否是一个
引用类型。
class Gen<T>
{
static Gen(){
if((object)T.default != null){
throw new ArgumentException(“T must be a reference type”);
}
}
}
20.1.7 访问受保护的成员
在一个泛型类声明中,对于继承的受保护的实例成员的访问是允许的,通过从泛型类构造的任何类型的
实例就可以做到。尤其是,用于访问§3.5.3中指定的protected和protected internal实例成员的规则,对于泛
型使用如下的规则进行了扩充。
在一个泛型类G中,对于一个继承的受保护的实例成员M,使用E.M的基本表达式是允许的,前提是E的
类型是一个从G构造的类类型,或继承于一个从G构造的类类型的类类型。
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 8/20 页)2005-9-21 10:24:15
第二章 泛型
在例子
class C<T>
{
protected T x;
}
class D<T> :C<T>
{
static void F(){
D<T> dt = new D<T>();
D<int> di = new D<int>();
D<string> ds = new D<string>();
dt.x = T.default;
di.x = 123;
ds.x = “test”;
}
}
三个对x的赋值语句都是允许的,因为它们都通过从泛型构造的类类型的实例发生。
20.1.8在泛型类中重载
在一个泛型类声明中的方法、构造函数、索引器和运算符可以被重载。但为了避免在构造类中的歧义,
这些重载是受约束的。在同一个泛型类声明中使用相同的名字声明的两个函数成员必须具有这样的参数
类型,也就是封闭构造类型中不能出现两个成员使用相同的名字和签名。当考虑所有可能的封闭构造类
型时,这条规则包含了在当前程序中目前不存在的类型是实参,但它仍然是可能出现的[1]。在类型参数
上的类型约束由于这条规则的目的而被忽略了。
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 9/20 页)2005-9-21 10:24:15
第二章 泛型
下面的例子根据这条规则展示了有效和无效的重载。
nterface I1<T> {⋯}
interface I2<T>{⋯}
class G1<U>
{
long F1(U u); //无效重载,G<int>将会有使用相同签名的两个成员
int F1(int i);
void F2(U u1, U u2); //有效重载,对于U没有类型参数
void F2(int I , string s); //可能同时是int和string
void F3(I1<U>a); //有效重载
void F3(I2<U>a);
void F4(U a); //有效重载
void F4(U[] a);}
class G2<U,V>
{
void F5(U u , V v); //无效重载,G2<int , int>将会有两个签名相同的成员
void F5(V v, U u);
void F6(U u , I1<V> v);//无效重载,G2<I1<int>,int>将会有两个签名相同的成员
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 10/20 页)2005-9-21 10:24:15
第二章 泛型
void F6(I1<V> v , U u);
void F7(U u1,I1<V> V2);//有效的重载,U不可能同时是V和I1<V>
void F7(V v1 , U u2);
void F8(ref U u); //无效重载
void F8(out V v);
}
class C1{⋯}
class C2{⋯}
class G3<U , V> where U:C1 where V:C2
{
void F9(U u); //无效重载,当检查重载时,在U和V上的约束将被忽略
void F9(V v);
}
20.1.9参数数组方法和类型参数
类型参数可以被用在参数数组的类型中。例如,给定声明
class C<V>
{
static void F(int x, int y ,params V[] args);
}
方法的扩展形式的如下调用
C<int>.F(10, 20);
C<object>.F(10,20,30,40);
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 11/20 页)2005-9-21 10:24:15
第二章 泛型
C<string>.F(10,20,”hello”,”goodbye”);
对应于如下形式:
C<int>.F(10,20, new int[]{});
C<object>.F(10,20,new object[]{30,40});
C<string>.F(10,20,new string[](“hello”,”goodbye”));
20.1.10重写和泛型类
在泛型类中的函数成员可以重写基类中的函数成员。如果基类是一个非泛型类型或封闭构造类型,那么
任何重写函数成员不能有包含类型参数的组成类型。然而,如果一个基类是一个开放构造类型,那么重
写函数成员可以使用在其声明中的类型参数。当重写基类成员时,基类成员必须通过替换类型实参而被
确定,如§20.5.4中所描述的。一旦基类的成员被确定,用于重写的规则和非泛型类是一样的。
下面的例子演示了对于现有的泛型其重写规则是如何工作的。
abstract class C<T>
{
public virtual T F(){⋯}
public virtual C<T> G(){⋯}
public virtual void H(C<T> x ){⋯}
}
class D:C<string>
{
public override string F(){⋯}//OK
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 12/20 页)2005-9-21 10:24:15
第二章 泛型
public override C<string> G(){⋯}//OK
public override void H(C<T> x); //错误,应该是C<string>
}
class E<T,U>:C<U>
{
public override U F(){⋯}//OK
public override C<U> G(){⋯}//OK
public override void H(C<T> x){⋯}//错误,应该是C<U>
}
20.1.11泛型类中的运算符
泛型类声明可以定义运算符,它遵循和常规类相同的规则。类声明的实例类型(§20.1.2)必须以一种类
似于运算符的常规规则的方式,在运算符声明中被使用,如下
一元运算符必须接受一个实例类型的单一参数。一元运算符“++”和“—”必须返回实例类型。
至少二元运算符的参数之一必须是实例类型。
转换运算符的参数类型和返回类型都必须是实例类型。
下面展示了在泛型类中几个有效的运算符声明的例子
class X<T>
{
public static X<T> operator ++(X(T) operand){⋯}
public static int operator *(X<T> op1, int op2){⋯}
public static explicit operator X<T>(T value){⋯}
}
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 13/20 页)2005-9-21 10:24:15
第二章 泛型
对于一个从源类型S到目标类型T的转换运算符,当应用§10.9.3中的规则时,任何关联S或T的类型参数被
认为是唯一类型,它们与其他类型没有继承关系,并且在这些类型参数上的任何约束都将被忽略。
在例子
class C<T>{⋯}
class D<T>:C<T>
{
public static implicit operator C<int>(D<T> value){⋯}//OK
public static implicit operator C<T>(D<T> value){⋯}//错误
}
第一个运算符声明是允许的,由于§10.9.3的原因,T和int被认为是没有关系的唯一类型。然而,第二个
运算符是一个错误,因为C<T>是D<T>的基类。
给定先前的例子,为某些类型实参声明运算符,指定已经作为预定义转换而存在的转换是可能的。
struct Nullable<T>
{
public static implicit operator Nullable<T>(T value){⋯}
public static explicit operator T(Nullable<T> value){⋯}
}
当类型object作为T的类型实参被指定,第二个运算符声明了一个已经存在的转换(从任何类型到object是
一个隐式的,也可以是显式的转换)。
在两个类型之间存在预定义的转换的情形下,在这些类型上的任何用户定义的转换都将被忽略。尤其是
如果存在从类型S到类型T的预定义的隐式转换(§6.1),所有用户定义的转换(隐式的或显式的)都将
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 14/20 页)2005-9-21 10:24:15
第二章 泛型
被忽略。
如果存在从类型S到类型T的预定义的显式转换,那么任何用户定义的从类型S到类型T的显式转换都将被
忽略。但用户定义的从S到T的隐式转换仍会被考虑。
对于所有类型除了object,由Nullable<T>类型声明的运算符都不会与预定义的转换冲突。例如
void F(int I , Nullable<int> n){
i = n; //错误
i = (int)n; //用户定义的显式转换
n = i; //用户定义的隐式转换
n = (Nullable<int>)i; //用户定义的隐式转换
}
然而,对于类型object,预定义的转换在所有情况隐藏了用户定义转换,除了一种情况:
void F(object o , Nullable<object> n){
o = n; //预定义装箱转换
o= (object)n; //预定义装箱转换
n= o; //用户定义隐式转换
n = (Nullable<object>)o; //预定义取消装箱转换
}
20.1.12泛型类中的嵌套类型
泛型类声明可以包含嵌套类型声明。封闭类的类型参数可以在嵌套类型中使用。嵌套类型声明可以包含
附加的类型参数,它只适用于该嵌套类型。
包含在泛型类声明中的每个类型声明是隐式的泛型类型声明。当编写一个嵌套在泛型类型内的类型的引
用时,包含构造类型,包括它的类型实参,必须被命名。然而,在外部类中,内部类型可以被无限制的
使用;当构造一个内部类型时,外部类的实例类型可以被隐式地使用。下面的例子展示了三个不同的引
用从Inner创建的构造类型的方法,它们都是正确的;前两个是等价的。
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 15/20 页)2005-9-21 10:24:15
第二章 泛型
class Outer<T>
{
class Inner<U>
{
static void F(T t , U u){⋯}
}
static void F(T t)
{
Outer<T>.Inner<string >.F(t,”abc”);//这两个语句有同样的效果
Inner<string>.F(t,”abc”);
Outer<int>.Inner<string>.F(3,”abc”); //这个类型是不同的
Outer.Inner<string>.F(t , “abc”); //错误,Outer需要类型参数
}
}
尽管这是一种不好的编程风格,但嵌套类型中的类型参数可以隐藏一个成员,或在外部类型中声明的一
个类型参数。
class Outer<T>
{
class Inner<T> //有效,隐藏了 Ouer的 T
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 16/20 页)2005-9-21 10:24:15
第二章 泛型
{
public T t; //引用Inner的T
}
}
20.1.13应用程序入口点
应用程序入口点不能在一个泛型类声明中。
20.2泛型结构声明
像类声明一样,结构声明可以有可选的类型参数。
struct-declaration:(结构声明:)
attributes opt struct-modifiers opt struct identifier type-parameter-list opt struct-interfaces opt type-parameterconstraints-
clauses opt struct-body ;opt
(特性可选 结构修饰符可选 struct 标识符 类型参数列表可选 结构接口可选 类型参数约束语句可选 结构
体;可选)
除了§11.3中为结构声明而指出的差别之外,泛型类声明的规则也适用于泛型结构声明。
20.3泛型接口声明
接口也可以定义可选的类型参数
interface-declaration:(接口声明:)
attribute opt interface-modifiers opt interface indentifier type-parameter-list opt
interface-base opt type-parameter-constraints-clause opt interface-body;
(特性可选 接口修饰符可选 interface 标识符 类型参数列表可选 基接口可选 类型参数约束语句可选 接口
体;可选)
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 17/20 页)2005-9-21 10:24:15
第二章 泛型
使用类型参数声明的接口是一个泛型接口声明。除了所指出的那些,泛型接口声明遵循和常规结构声明
相同的规则。
在接口声明中的每个类型参数在接口的声明空间定义了一个名字。在一个接口上的类型参数的作用域包
括基接口、类型约束语句和接口体。在其作用域之内,一个类型参数可以被用作一个类型。应用到接口
上的类型参数和应用到类(§20.1.1)上的类型参数具有相同的限制。
在泛型接口中的方法与泛型类(§20.1.8)中的方法遵循相同的重载规则。
20.3.1实现接口的唯一性
由泛型类型声明实现的接口必须为所有可能的构造类型保留唯一性。没有这条规则,将不可能为特定的
构造类型确定调用正确的方法。例如,假定一个泛型类声明允许如下写法。
interface I<T>
{
void F();
}
class X<U, V>:I<U>,I<V> //错误,I<U>和I<V>冲突
{
void I<U>.F(){⋯}
void I<V>.F(){⋯}
}
如果允许这么写,那么下面的情形将无法确定执行那段代码。
I<int> x = new X<int ,int>();
x.F();
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 18/20 页)2005-9-21 10:24:15
第二章 泛型
为了确定一个泛型类型声明的接口列表是有效的,可以按下面的步骤进行。
让L成为在泛型类、结构或接口声明 C中指定的接口的列表。
将任何已经在L中的接口的基接口添加到L
从L中删除任何重复的接口
在类型实参被替换到L后,如果任何从C创建的可能构造类型,导致在L中的两个接口是同一的,那么C的
声明是无效的。当确定所有可能的构造类型时,约束声明不予考虑。
在类声明X之上,接口列表L由I<U>和I<V>组成。该声明是无效的,因为任何使用相同类型U和V的构造
类型,将导致这两个接口是同一的。
20.3.2显式接口成员实现
使用构造接口类型的显式接口成员实现本质上与简单接口类型方式上是相同的。和以往一样,显式接口
成员实现必须由一个指明哪个接口被实现的接口类型而限定。该类型可能是一个简单接口或构造接口,
如下例子所示。
interface IList<T>
{
T[] GetElement();
}
interface IDictionary<K,V>
{
V this[K key];
Void Add(K key , V value);
}
class List<T>:IList<T>,IDictionary<int , T>
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 19/20 页)2005-9-21 10:24:15
第二章 泛型
{
T[] IList<T>.GetElement(){⋯}
T IDictionary<int , T>.this[int index]{⋯}
void IDictionary<int , T>.Add(int index , T value){⋯}
}
--------------------------------------------------------------------------------
[1] 也就是在类型参数被替换成类型实参时,有可能替换后的实参导致出现两个成员使用相同的名字和签
名。
file:///E|/mybook/C#2.0/C#2.0新特性/2.htm(第 20/20 页)2005-9-21 10:24:15
第三章 匿名方法
第三章 匿名方法
原著:Microsoft Corporation
原文:
http://msdn.microsoft.com/vcsharp/team/language/default.aspx (SpecificationVer2.doc)
翻译:lover_P
出处:
--------------------------------------------------------------------------------
[内容]
3.1 匿名方法表达式
3.2 匿名方法签名
3.3 匿名方法转换
3.3.1 委托建立表达式
3.4 匿名方法块
3.5 外部变量
3.5.1 捕获外部变量
3.5.2 局部变量的实例化
3.6 匿名方法求值
3.7 委托实例相等性
3.8 明确赋值
3.9 方法组转换
3.10 实现实例
3.1 匿名方法表达式
匿名方法表达(anonymous-method-expression)式定义了匿名方法(anonymous method),并求得一个
引用了该方法的特殊的值。
primary-no-array-creation-expression:
⋯
anonymous-method-expression
anonymous-method-expression:
delegate anonymous-method-signatureopt block
anonymous-method-signature:
( anonymous-method-parameter-listopt )
anonymous-method-parameter-list:
anonymous-method-parameter
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 1/15 页)2005-9-21 10:24:18
第三章 匿名方法
anonymous-method-parameter-list , anonymous-method-parameter
anonymous-method-parameter:
parameter-modifieropt type identifier
初等非数组表达式:
...
匿名方法表达式
匿名方法表达式:
delegate 匿名方法签名可选 块
匿名方法签名:
( 匿名方法参数列表可选 )
匿名方法参数列表:
匿名方法参数
匿名方法参数 , 匿名方法参数
匿名方法参数:
参数修饰符可选 类型 标识符
匿名方法表达(anonymous-method-expression)是一个遵从特殊转换规则(见3.3)的值。这个值没有类
型,但可以隐式地转换为一个兼容的委托类型。
匿名方法表达式(anonymous-method-expression)为参数、局部变量和常量以及标签定义了一个新的声
明空间。
3.2 匿名方法签名
可选的匿名方法签名(anonymous-method-signature)为匿名方法的形式参数定义了名字和类型。这些参
数的作用于是匿名方法的块(block)。如果一个局部变量的作用域包含了匿名方法表达式(anonymousmethod-
expression),且该匿名方法的参数和该局部变量相同,则会产生编译错误。
如果一个匿名方法表达式(anonymous-method-expression)具有匿名方法签名(anonymous-methodsignature),
则与之兼容的委托类型被强制具有相同的参数类型和修饰符,且具有相同顺序(见3.3)。如
果一个匿名方法表达式(anonymous-method-expression)没有匿名方法签名(anonymous-methodsignature),
则与之相兼容的委托类型被强制要求没有out参数。
注意匿名方法签名(anonymous-method-signature)不能包含特性或参数数组(译注:用于实现变长参数
列表)。然而,一个匿名方法签名(anonymous-method-signature)可以和一个包含参数数组的委托类型
相兼容。
3.3 匿名方法转换
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 2/15 页)2005-9-21 10:24:18
第三章 匿名方法
匿名方法表达式(anonymous-method-expression)是一个没有类型的特殊值。一个匿名方法表达式
(anonymous-method-expression)可以用于委托建立表达式(delegate-creation-expression)(见3.3.1)。对
于匿名方法表达式(anonymous-method-expression)的其他有效的应用取决于定义于其上的隐式转换。
匿名方法表达式(anonymous-method-expressio)与任何兼容的委托类型之间均存在隐式转换。如果D是
一个委托类型,而A是一个匿名方法表达式(anonymous-method-expression),当且仅当以下两个条件成
立的时候D和A是兼容的。
首先,D的参数类型必须与A兼容:
如果A不含匿名方法签名(anonymous-method-signature),则D可以具有任意类型的零或多个参数,但
这些参数不能带有out修饰符。
如果具有匿名方法签名(anonymous-method-signature),则D必须具有和A形同数量的参数,A中的每
个参数必须和D中相应的参数具有相同的类型,并且A中每个参数上的ref或out修饰符的出现与否必须与D
中的相应参数相同。如果D中的最后一个参数是参数数组,则没有相互兼容的A和D。
其次,D的返回值类型必须与A兼容。由于这一规则,A中不能包含其他匿名方法的块(block)。
如果D的返回值类型被声明为void,则A中包含的所有return语句不能指定表达式。
如果D的返回值类型被声明为R,则A中包含的所有return语句不许指定一个能够隐式转换为R的表达
式。A中的块(block)的终点必须可达。
除了和相兼容的委托类型之间的隐式转换,匿名方法表达式(anonymous-method-expression)与任何类
型之间不存在任何转换,包括object类型。
下面的例子详细地解释了这些规则:
delegate void D(int x);
D d1 = delegate { }; // 正确
D d2 = delegate() { }; // 错误,签名不匹配
D d3 = delegate(long x) { }; // 错误,签名不匹配
D d4 = delegate(int x) { }; // 正确
D d5 = delegate(int x) { return; }; // 正确
D d6 = delegate(int x) { return x; }; // 错误,返回值不匹配
delegate void E(out int x);
E e1 = delegate { }; // 错误,E带有一个输出参数
E e2 = delegate(out int x) { x = 1; }; // 正确
E e3 = delegate(ref int x) { x = 1; }; // 错误,签名不匹配
delegate int P(params int[] a);
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 3/15 页)2005-9-21 10:24:18
第三章 匿名方法
P p1 = delegate { }; // 错误,块的结尾不可达
P p2 = delegate { return; }; // 错误,返回值类型不匹配
P p3 = delegate { return 1; }; // 正确
P p4 = delegate { return "Hello"; }; // 错误,返回值类型不匹配
P p5 = delegate(int[] a) { // 正确
return a[0];
};
P p6 = delegate(params int[] a) { // 错误,(译注:多余的)params修饰符
return a[0];
};
P p7 = delegate(int[] a) { // 错误,返回值类型不匹配
if(a.Length > 0) return a[0];
return "Hello";
};
delegate object Q(params int[] a);
Q q1 = delegate(int[] a) { // 正确
if(a.Length > 0) return a[0];
return "Hello";
};
3.3.1 委托建立表达式
委托建立表达式(delegate-creation-expression)可以用于匿名方法和委托类型之间的转换语法的替代
品。如果一个委托建立表达式(delegate-creation-expression)的参数表达式(expression)是一个匿名方法
表达式(anonymous-method-expression),则匿名方法依照上面定义的隐式转换规则转换为给定的委托类
型。例如,如果D是一个委托类型,则表达式
new D(delegate { Console.WriteLine("hello"); })
等价于表达式
(D) delegate { Console.WriteLine("hello"); }
3.4 匿名方法块
匿名方法表达式(anonymous-method-expression)的块(block)遵从下列规则:
如果匿名方法包含一个签名,则签名中指定的参数在块(block)中是可用的。如果匿名方法不包含签
名,则它可以转换为一个带有参数的委托类型(见3.3),但这些参数在块(block)中无法访问。
除非在最贴切的匿名方法的签名(如果有的话)中指定了ref或out参数,否则在块中访问ref或out参数会发
生编译期间错误。
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 4/15 页)2005-9-21 10:24:18
第三章 匿名方法
当this的类型是一个结构类型时,当在块(block)中访问this是一个编译错误,不论这种能够访问是显式
的(如this.x)还是隐式的(如x,而x是该结构的一个实例方法)。这一规则简单地禁止了这种访问,从
而不会对结构的成员的查找结果产生影响。
块(block)可以访问匿名方法外部的变量(见3.5)。对外部变量的访问将会引用到变量的实例,该变量
在匿名方法表达式(anonymous-method-expression)求值的过程中应当是活动的(见3.6)。
如果块(block)中包含的goto语句、break语句或continue语句的目标在块(block)的外面或在块
(block)中包含的一个匿名方法中,则会产生编译错误。
块(block)中的return语句将控制从最近的一个匿名方法的调用中返回,而不是从函数成员中返回。
return语句中指定的表达式必须和匿名方法表达式(anonymous-method-expression)转换(见3.3)得到的
委托类型相匹配。
除了计算和调用匿名方法表达式(anonymous-method-expression)外,对于块(block)的执行方式没有
任何明确的限制。特别地,编译器会选择通过合成个或多个命名了的方法或类型来实现一个匿名方法。
这些合成的名字必须保留在编译器所使用的空间中:这些名字必须包含两个连续的下划线。
3.5 外部变量
若一个匿名方法表达式(anonymous-method-expression)包含在任何局部变量、值参数或参数数组的作
用域中,则称它们(译注:指那些局部变量、值参数或参数数组)为该匿名方法表达式(anonymousmethod-
expression)的外部变量(outer variable)。在一个类的实例函数成员中,this被认为是一个值参
数,并且是该函数成员中所包含的任何匿名方法表达式(anonymous-method-expression)的外部变量。
3.5.1 捕获外部变量
当在一个匿名方法中引用一个外部变量时,称该外部变量被匿名方法所捕获。通常,一个局部变量的
生存期是块或与之关联的语句的执行的结束点。然而,一个被捕获的外部变量的生存期将持续到引用了
匿名方法的委托符合垃圾收集的条件时。
在下面的例子中:
using System;
delegate int D();
class Test {
static D F() {
int x = 0;
D result = delegate { return ++x; }
return result;
}
static void Main() {
D d = F();
Console.WriteLine(d());
Console.WriteLine(d());
Console.WriteLine(d());
}
}
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 5/15 页)2005-9-21 10:24:18
第三章 匿名方法
局部变量x被匿名方法所捕获,x的生存期至少被延续到从F所返回的委托符合垃圾收集条件时(这并不
一定发生在程序的最末尾)。由于每个匿名方法的调用均操作了x的同一个实例,这个例子的输出将会
是:
1
2
3
当一个局部变量或一个值参数被一个匿名方法所捕获,该局部变量获知参数将不再被认为是一个固定
变量,而是被认为是一个可移动的变量。因此,任何unsafe代码如果记录了该外部变量的地址,则必须首
先使用fixed语句来固定这个变量。
3.5.2 局部变量的实例化
当程序执行到一个变量的作用域中时,则该局部变量被实例化。例如,当下面的方法被调用时,局部
变量x被实例化和初始化三次——每当循环迭代一次时。
static void F() {
for(int i = 0; i < 3; i++) {
int x = i * 2 + 1;
...
}
}
然而,将x的声明移到循环外面则只会引起x的一次实例化:
static void F() {
int x;
for(int i = 0; i < 3; i++) {
x = i * 2 + 1;
...
}
}
通常,没有办法观察到一个局部变量被实例化过多少次——因为实例化的生存期是脱节的,而上每次
实例化都简单地使用相同的存贮空间是可能的。然而,当一个匿名方法捕获了一个局部变量,实例化的
效果就明显了。下面的例子
using System;
delegate void D();
class Test {
static D[] F() {
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 6/15 页)2005-9-21 10:24:18
第三章 匿名方法
D[] result = new D[3];
for(int i = 0; i < 3; i++) {
int x = i * 2 + 1;
result[i] = delegate { Console.WriteLine(x); };
}
return result;
}
static void Main() {
foreach (D d in F()) d();
}
}
的输出为:
1
3
5
然而,当x的声明被移到循环外面时:
static D[] F() {
D[] result = new D[3];
int x;
for(int i = 0; i < 3; i++) {
x = i * 2 + 1;
result[i] = delegate { Console.WriteLine(x); };
}
return result;
}
结果为:
5
5
5
注意,根据判等操作(见3.7),由上面这个版本的F方法所建立的三个委托是相等的。另外,编译器可
以(但不是必须)将这三个实例优化为一个单独的委托实例(见3.6)。
匿名方法委托可以共享一些捕获的变量,而具有另一些捕获变量的单独的实例。例如,如果F变为
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 7/15 页)2005-9-21 10:24:18
第三章 匿名方法
static D[] F() {
D[] result = new D[3];
int x = 0;
for(int i = 0; i < 3; i++) {
int y = 0;
result[i] = delegate { Console.WriteLine("{0} {1}", ++x, ++y); };
}
return result;
}
这三个委托捕获了x的相同的(一个)实例,而捕获y的单独的实例。输出为:
1 1
2 1
3 1
单独的匿名方法可以捕获一个外部变量的相同的实例。下面的例子中:
using System;
delegate void Setter(int value);
delegate int Getter();
class Test {
static void Main() {
int x = 0;
Setter s = delegate(int value) { x = value; };
Getter g = delegate { return x; };
s(5);
Console.WriteLine(g());
s(10);
Console.WriteLine(g());
}
}
两个匿名方法捕获了局部变量x的相同的实例,它们可以通过改变量进行“通信”。这个例子的输出为:
5
10
3.6 匿名方法求值
运行时对匿名方法表达式的求值将得到一个委托实例,该委托实例引用了这个匿名方法和一组活动的
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 8/15 页)2005-9-21 10:24:18
第三章 匿名方法
外部变量的集合(可能为空)。当一个由匿名方法表达式求得的委托被调用时,匿名方法体将被执行。
方法体中的代码可以使用由委托所引用的一组捕获了的外部变量。
有匿名方法表达式得到的委托的调用链表包含一个唯一的入口点。委托的确切的目标对象和目标方法
是未定义的。尤其是委托的目标对象是否为null、函数成员内部的this值或其他对象也是未定义的。
对带有相同的一组(可能为空)捕获的外部变量的语义上一致的匿名方法表达式的求值可以(但不是
必须)返回相同的委托实例。这里用的术语“语义上一致的”表示任何情况下对匿名方法的执行对同样
的参数应该产生同样的效果。这一规则允许对代码作如下优化:
delegate double Function(double x);
class Test {
static double[] Apply(double[] a, Function f) {
double[] result = new double[a.Length];
for(int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}
static void F(double[] a, double[] b) {
a = Apply(a, delegate(double x) { return Math.Sin(x); });
b = Apply(b, delegate(double y) { return Math.Sin(y); });
...
}
}
由于两个匿名方法委托具有相同的一组捕获的外部变量(为空),而且匿名方法在语义上是一致的,
编译器可以令这两个委托引用一个相同的目标方法。甚至对于这两个匿名方法表达式,编译器可以返回
完全一样的委托实例。
3.7 委托实例相等性
下面的规则决定着匿名方法委托实例的判等操作符和Object.Equals方法的结果:
从语义上相同的带有相同一组(也可能是都没有没有)被捕获变量的匿名方法表达式(anonymousmethod-
expressions)所产生的委托实例可以(但不是必须)相等。
从语义上相同的单被捕获变量不同的的匿名方法表达式(anonymous-method-expressions)所产生的委托
实例必须不相等。
3.8 明确赋值
对匿名方法参数的明确赋值规则和命名方法(named method,区别于匿名方法)参数的明确复制规定一
样。也就是说,引用参数和值参数必须同国明确的赋值进行初始化,而输出参数可以不比进行明确赋
值。
另外,如果要在匿名方法正常返回之前使用输出参数,则输出参数必须被明确赋值。
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 9/15 页)2005-9-21 10:24:18
第三章 匿名方法
当控制转移到匿名方法表达式的块中时,对于一个外部变量v的明确赋值规定与匿名方法表达式之前对v
的明确赋值规定一样。
对于一个匿名方法后面的变量v的明确赋值规定和匿名方法表达式之前的明确赋值规定相同。
下面的例子:
delegate bool Filter(int i);
void F() {
int max;
// 错误,max没有被明确赋值
Filter f = delegate(int n) { return n < max; }
max = 5;
DoWork(f);
}
会产生一个编译错误,因为在匿名方法声明之前max没有被明确赋值。下面的例子
delegate void D();
void F() {
int n;
D d = delegate { n = 1; };
d();
// 错误,n没有被明确赋值
Console.WriteLine(n);
}
同样会产生变异错误,因为匿名方法中对n的赋值不会影响匿名方法外部对n的明确赋值。
3.9 方法组转换
和3.3节中描述的匿名方法隐式转换类似,从一个方法组到一个兼容的委托类型之间也存在一个隐式的
转换。
对于一个给定的方法组E和一个委托类型D,如果允许形为new D(E)的委托建立表达式(见2.9.6),则
存在E到D的隐式转换,
下面的例子:
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 10/15 页)2005-9-21 10:24:18
第三章 匿名方法
using System;
using System.Windows.Forms;
class AlertDialog {
Label message = new Label();
Button okButton = new Button();
Button cancelButton = new Button();`
public AlertDialog() {
okButton.Click += new EventHandler(OkClick);
cancelButton.Click += new EventHandler(CancelClick);
...
}
void OkClick(object sender, EventArgs e) {
...
}
void CancelClick(object sender, EventArgs e) {
...
}
}
构造器使用两个new运算符建立了两个委托实例。隐式方法组转换允许将其写作更短的形式:
public AlertDialog() {
okButton.Click += OkClick;
cancelButton.Click += CancelClick;
...
}
与其它隐式和显式转换相同,转换运算符可以显式地用于一个特定的转换中。因此,下面的例子:
object obj = new EventHandler(myDialog.OkClick);
可以写作:
object obj = (EventHandler)myDialog.OkClick;
方法组和匿名方法表达式会影响到重载抉择,但不会参与类型推断。更多细节参见2.6.4节
3.10 实现实例
这一节将讨论一些标准C#构造中匿名方法的可能的实现。这里描述的实现和Visual C#编译器基于相同的
原理,但这并不是必须的实现,而只是一种可能。
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 11/15 页)2005-9-21 10:24:18
第三章 匿名方法
本节的最后将给出包含了不同特征的匿名方法的代码实例。对于每个例子,转换得到的相应代码仅使
用了标准C#提供的构造。这些例子中,假设标识符D为下面这样的委托类型:
public delegate void D();
最简单的匿名方法是不捕获任何外部变量的匿名方法:
class Test {
static void F() {
D d = delegate { Console.WriteLine("test"); };
}
}
它会被翻译为一个委托实例,它引用了一个编译器产生的静态方法,这个方法中包含了匿名方法中的
代码:
class Test {
static void F() {
D d = new D(__Method1);
}
static void __Method1() {
Console.WriteLine("test");
}
}
In the following example, the anonymous method references instance members of this:
下面的例子中,你名方法引用了this的实例成员:
class Test {
int x;
void F() {
D d = delegate { Console.WriteLine(x); };
}
}
This can be translated to a compiler generated instance method containing the code of the anonymous method:
它会被翻译为一个编译器生成的实例方法,该方法包含了匿名方法中的代码:
class Test {
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 12/15 页)2005-9-21 10:24:18
第三章 匿名方法
int x;
void F() {
D d = new D(__Method1);
}
void __Method1() {
Console.WriteLine(x);
}
}
In this example, the anonymous method captures a local variable:
在这个例子中,匿名方法捕获了一个局部变量:
class Test {
void F() {
int y = 123;
D d = delegate { Console.WriteLine(y); };
}
}
The lifetime of the local variable must now be extended to at least the lifetime of the anonymous method delegate. This
can be achieved by “lifting” the local variable into a field of a compiler generated class. Instantiation of the local
variable (§21.5.2) then corresponds to creating an instance of the compiler generated class, and accessing the local
variable corresponds to accessing a field in the instance of the compiler generated class. Furthermore, the anonymous
method becomes an instance method of the compiler generated class:
局部变量的生存期现在必须扩展为至少持续到匿名方法委托的生存期结束。这可以通过将局部变
量“提升”到一个编译器生成的类的域中来完成。局部变量的实例化(见3.5.2)相当于建立编译器生成
的类的一个实例,而访问局部变量相当于访问编译器建立的类的这个实例的域。另外,匿名方法变成编
译器生成的类的一个实例方法:
class Test {
void F() {
__locals1 = new __Locals1();
__locals1.y = 123;
D d = new D(__locals1.__Method1);
}
class __Locals1 {
public int y;
public void __Method1() {
Console.WriteLine(y);
}
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 13/15 页)2005-9-21 10:24:18
第三章 匿名方法
}
}
Finally, the following anonymous method captures this as well as two local variables with different lifetimes:
最后,下面的匿名方法捕获了this和两个具有不同生存期的局部变量:
class Test {
int x;
void F() {
int y = 123;
for (int i = 0; i < 10; i++) {
int z = i * 2;
D d = delegate { Console.WriteLine(x + y + z); };
}
}
}
Here, a compiler generated class is created for each statement block in which locals are captured such that the locals in
the different blocks can have independent lifetimes. An instance of __Locals2, the compiler generated class for the
inner statement block, contains the local variable z and a field that references an instance of __Locals1. An instance of
__Locals1, the compiler generated class for the outer statement block, contains the local variable y and a field that
references this of the enclosing function member. With these data structures it is possible to reach all captured outer
variables through an instance of __Local2, and the code of the anonymous method can thus be implemented as an
instance method of that class.
这里,编译器为每一个捕获了局部变量的语句块分别都生成了一个类。因此,那些在不同的块中所捕
获的局部变量具有独立的生存期。编译器为内层语句块建立的类__Locals2的一个实例,包含了局部变量z
和一个引用了__Locals1的实例的域。编译器为外层语句块建立的类__Locals1的一个实例,包含了局部变
量y和一个引用了函数成员所在类的this的一个域。通过这些数据结构,可以通过__Locals2的一个实例来获
得所有捕获的外部变量,而匿名方法中的代码因此被实现为该类的一个实例方法。
class Test {
void F() {
__locals1 = new __Locals1();
__locals1.__this = this;
__locals1.y = 123;
for (int i = 0; i < 10; i++) {
__locals2 = new __Locals2();
__locals2.__locals1 = __locals1;
__locals2.z = i * 2;
D d = new D(__locals2.__Method1);
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 14/15 页)2005-9-21 10:24:18
第三章 匿名方法
}
}
class __Locals1 {
public Test __this;
public int y;
}
class __Locals2 {
public __Locals1 __locals1;
public int z;
public void __Method1() {
Console.WriteLine(__locals1.__this.x + __locals1.y + z);
}
}
}
file:///E|/mybook/C#2.0/C#2.0新特性/3.htm(第 15/15 页)2005-9-21 10:24:18
第四章 迭代器
第四章 迭代器
原著:Microsoft Corporation
原文:
http://msdn.microsoft.com/vcsharp/team/language/default.aspx (SpecificationVer2.doc)
翻译:lover_P
出处:
http://www.csdn.net/Develop/article/26/26043.shtm
--------------------------------------------------------------------------------
[内容]
4.1 迭代器块
4.1.1 枚举器接口
4.1.2 可枚举接口
4.1.3 生成的类型
4.1.4 this访问
4.2 Enumerator对象
4.2.1 MoveNext()方法
4.2.2 Current属性
4.2.3 Dispose()方法
4.3 Enumerable对象
4.3.1 GetEnumerator()方法
4.4 yield语句
4.4.1 有限赋值
4.5 实例
4.1 迭代器块
一个迭代器块(iterator block)是一个能够产生有序的值序列的块。迭代器块和普通语句块的区别就是
其中出现的一个或多个yield语句。
yield return语句产生迭代的下一个值。
yield break语句表示迭代完成。
只要相应的函数成员的返回值类型是一个枚举器接口(见4.1.1)或是一个可枚举接口(见4.1.2),就可
以 将一个迭代器块用作方法体、运算符体或访问器体。
迭代器块并不是C#语法中的独立元素。它们受多种因素的制约,并且对函数成员声明的语义有很大影
响,但在语法上它们只是块(block)。
当一个函数成员用一个迭代器块来实现时,如果函数成员的形式参数列表指定了ref或out参数,则会引
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 1/14 页)2005-9-21 10:24:23
第四章 迭代器
起编译错误。
如果在迭代器块中出现了return语句,则会引起编译错误(但yield return语句是允许的)。
如果迭代器块包含不安全上下文,则会引起编译错误。一个迭代器块必须定义在一个安全的上下文
中,即使它的声明嵌套在一个不安全上下文中。
4.1.1 枚举器接口
枚举器(enumerator)接口包括非泛型接口System.Collections.IEnumerator和泛型接口System.Collections.
Generic.IEnumerator<T>的所有实例化。在这一章中,这些接口将分别称作IEnumerator和
IEnumerator<T>。
4.1.2 可枚举接口
可枚举(enumerable)接口包括非泛型接口System.Collections.IEnumerable和泛型接口System.Collections.
Generic.IEnumerable<T>。在这一章中,这些接口分别称作IEnumerable和IEnumerable<T>。
4.1.3 生成类型
一个迭代器块能够产生一个有序的值序列,其中所有的制具有相同的类型。这个类型称为迭代器块的
生成类型(yield type)。
用于实现一个返回IEnumerator或IEnumerable的函数成员的迭代器块的生成类型为object。
用于实现一个返回IEnumerator<T>或IEnumerable<T>的函数成员的迭代器块的生成类型为T。
4.1.4 this访问
在一个类的一个实例成员中的迭代器块里,表达式this是一个值。这个值的类型就是出现这种用法的
类,并且它是对被调用的方法所在的对象的一个引用。
在一个结构的一个实例成员中的迭代器块里,表达式this是一个变量。这个变量的类型就是出现这种用
法的结构 ,这个变量存贮了对被调用成员所在结构的一个拷贝。结构的实例成员中的迭代器块里的this变
量和以该结构为类型的值变量完全一样。
4.2 Enumerator对象
如果一个函数成员使用了迭代器块来返回一个枚举器接口类型,对该函数成员的调用不会立即执行迭
代器块中的代码,而是建立并返回一个枚举器对象。这个对象封装了迭代器块中指定的代码,而对迭代
器中指定的代码的执行发生在调用该枚举器对象的MoveNext()方法时。一个枚举器对象具有如下特征:
它实现了IEnumerator和IEnumerator<T>,这里T是迭代器块的生成类型。
它实现了System.IDisposable。
它用传递给函数成员的参数值(如果有的话)和实例值进行初始化。
它有四个可能的状态:before、running、suspended和after,其初始状态为before。
典型的枚举器对象是由编译器自动生成的封装了迭代器块中的代码并实现了枚举器接口的枚举器类的
实例,但其他的实现也是允许的。如果一个枚举器类是由编译器自动生成的,则该类是直接或间接地嵌
套在 包含了函数成员的类中的,具有私有的可访问性,并且具有一个由编译器保留使用的名字。
一个枚举器对象可以实现上面所述之外的其它接口。
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 2/14 页)2005-9-21 10:24:23
第四章 迭代器
后面的几节详细地描述了由一个枚举器对象所实现的IEnumerable和IEnumerable<T>接口中的MoveNext
()、Current和Dispose()成员的确切行为。
注意,枚举器对象不支持IEnumerator.Reset()方法。调用该方法会抛出System.NotSupporteException异
常。
4.2.1 MoveNext()方法
枚举器对象的MoveNext()方法封装了迭代器块的代码。对MoveNext()方法的调用执行了迭代器块中的代
码,并为枚举器对象的Current属性设置一个适当的值。MoveNext()方法完成 的确切动作取决于调用
MoveNext()方法是枚举器对象的状态:
如果枚举器对象的状态为before,调用MoveNext()方法:
将状态设置为running。
将迭代器块对象的参数(包括this)初始化为枚举器对象初始化时所保存的变量值和实例值。
从执行迭代器块的开始执行,直到被中断(将在下面讨论)。
如果枚举器对象的状态为running,则调用MoveNext()方法的结果未指定。
如果枚举器对象的状态为suspended,调用MoveNext()方法:
将状态设置为running。
将所有局部变量和参数(包括this)恢复为迭代器块执行过程中最后一次挂起时所保存的值。注意这些变
量所引用的对象的内容在上一次MoveNext()后可能会发生改变。
从上一次执行中断所在的yield return语句继续执行迭代器块,直到执行再次被中断(将在下面讨论)。
如果枚举器对象的状态为after,调用MoveNext()方法将返回false。
当MoveNext()方法执行迭代器块时,执行过程会通过四种途径中断:yield return语句、yield break语句、
遇到迭代器块的结尾以及迭代器块中抛出了异常并被传播到块外。
当遇到yield return语句(见4.4)时:
对语句中给定的表达式进行求值,隐式转换为生成类型,并赋给枚举器对象的Current属性。
挂起迭代器体的执行过程。保存所有局部变量和参数(包括this)的值,以及这个yield return语句的位
置。如果该yield return语句位于一个或多个try块中,则与之相关联的finally块在此时还不会被执行。
将枚举器对象的状态设置为suspended。
向MoveNext()方法的调用者返回true,表示迭代已经成功地转移到下一个值上。
当遇到yield break语句(见4.4)时:
如果该yield break语句位于一个或多个try块中,则执行与之相关联的finally块。
将枚举器对象的状态设置为after。
向MoveNext()方法的调用者返回false,表示迭代完成。
当遇到迭代器快的结尾时:
将枚举器对象的状态设置为after。
向MoveNext()方法的调用者返回false,表示迭代完成。
当迭代器快抛出了一个异常,并传播到块外时:
迭代器块中适当的finally块将被执行。
将枚举器对象的状态设置为after。
将异常传播给MoveNext()方法的调用者。
4.2.2 Current属性
一个枚举器对象的Current属性受迭代器块中的yield return语句的影响。
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 3/14 页)2005-9-21 10:24:23
第四章 迭代器
当一个枚举器对象处于suspended状态时,Current属性的值由最后一次对MoveNext()方法的调用设置。
当一个枚举器对象处于before、running或after状态时,访问Current属性的结果是未定义的。
For an iterator block with a yield type other than object, the result of accessing Current through the enumerator
object’s IEnumerable implementation corresponds to accessing Current through the enumerator object’s
IEnumerator<T> implementation and casting the result to object.
如果一个迭代器块的生成类型不是object,通过枚举器对象实现的IEnumerable以及相应的
IEnumerator<T>对Current的访问会将结果转换为object。
4.2.3 The Dispose method
4.2.3 Dispose()方法
The Dispose method is used to clean up the iteration by bringing the enumerator object to the after state.
? If the state of the enumerator object is before, invoking Dispose changes the state to after.
? If the state of the enumerator object is running, the result of invoking Dispose is unspecified.
? If the state of the enumerator object is suspended, invoking Dispose:
Changes the state to running.
Executes any finally blocks as if the last executed yield return statement were a yield break statement. If this causes an
exception to be thrown and propagated out of the iterator body, the state of the enumerator object is set to after and
the exception is propagated to the caller of the Dispose method.
Changes the state to after.
? If the state of the enumerator object is after, invoking Dispose has no affect.
Dispose()方法通过将枚举器对象的状态设置为after来清除迭代器。
如果枚举器对象的状态为before,调用Dispose()方法将其状态设置为after。
如果枚举器对象的状态为running,调用Dispose()方法的结果是未定义的。
如果枚举器对象的状态为suspended,调用Dispose()方法:
将状态设置为running。
执行所有的finally块,好像yield return语句是yield break语句一样。如果这导致了异常被抛出并传播到迭代
器块外,则将枚举器对象的状态设置为after并将异常传播给Dispose()方法的调用者。
将砖塔设置为after。
如果枚举器对象的状态为after,调用Dispose()方法没有任何效果。
4.3 Enumerable objects
4.3 Enumerable对象
When a function member returning an enumerable interface type is implemented using an iterator block, invoking the
function member does not immediately execute the code in the iterator block. Instead, an enumerable object is
created and returned. The enumerable object’s GetEnumerator method returns an enumerator object that
encapsulates the code specified in the iterator block, and execution of the code in the iterator block occurs when the
enumerator object’s MoveNext method is invoked. An enumerable object has the following characteristics:
当一个返回一个可枚举接口类型的函数成员使用了迭代器块时,对该函数成员的调用不会立即执行迭
代器块中的代码,而是建立并返回一个可枚举对象。该可枚举对象有一个GetEnumerator()方法,能够返
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 4/14 页)2005-9-21 10:24:23
第四章 迭代器
回一个枚举器对象。该枚举器对象封装了迭代器块中指定的代码,当调用这个枚举器对象的MoveNext()
方法时,会执行迭代器块中的代码。一个可枚举对象具有如下特征:
? It implements IEnumerable and IEnumerable<T>, where T is the yield type of the iterator block.
? It is initialized with a copy of the argument values (if any) and instance value passed to the function member.
它实现了IEnumerable或IEnumerable<T>,这里T是迭代器块的生成类型。
它用传递给函数成员的参数值(如果有的话)和实例值进行初始化。
An enumerable object is typically an instance of a compiler-generated enumerable class that encapsulates the code in
the iterator block and implements the enumerable interfaces, but other methods of implementation are possible. If an
enumerable class is generated by the compiler, that class will be nested, directly or indirectly, in the class containing the
function member, it will have private accessibility, and it will have a name reserved for compiler use (§2.4.2).
典型的可枚举对象是由编译器自动生成的封装了迭代器块中的代码并实现了可枚举接口的可枚举类的
实例,但其他的实现也是允许的。如果一个可枚举类是由编译器自动生成的,则该类是直接或间接地嵌
套在函数成员中的,具有私有的可访问性,并且具有一个由编译器保留使用的名字。
An enumerable object may implement more interfaces than those specified above. In particular, an enumerable object
may also implement IEnumerator and IEnumerator<T>, enabling it to serve as both an enumerable and an
enumerator. In that type of implementation, the first time an enumerable object’s GetEnumerator method is
invoked, the enumerable object itself is returned. Subsequent invocations of the enumerable object’s
GetEnumerator, if any, return a copy of the enumerable object. Thus, each returned enumerator has its own state and
changes in one enumerator will not affect another.
一个可枚举对象可以实现上述之外的其它接口。例如, 一个可枚举对象还可以实现IEnumerator和
IEnumerator<T>,使得它既是可枚举的又是一个枚举器。这种情况下,当可枚举对象的GetEnumerator()方
法第一次被调用时,将返回可枚举对象本身。以后对可枚举对象的GetEnumerator()方法的调用(如果有
的话),将返回可枚举对象的一个拷贝。因此,每个被返回的枚举器具有其自己的状态,并且一个枚举
器和其它枚举器互不影响。
4.3.1 The GetEnumerator method
4.3.1 GetEnumerator()方法
An enumerable object provides an implementation of the GetEnumerator methods of the IEnumerable and
IEnumerable<T> interfaces. The two GetEnumerator methods share a common implementation that acquires and
returns an available enumerator object. The enumerator object is initialized with the argument values and instance
value saved when the enumerable object was initialized, but otherwise the enumerator object functions as described in
§22.2.
一个可枚举对象提供了对IEnumerator和IEnumberator<T>接口的GetEnumerator()方法的实现。两个
GetEnumerator()方法共享一个实现,能够获取并返回一个有效的枚举器对象。该枚举器对象使用可枚举
对象被初始化时所保存的参数值和实例值进行初始化,该枚举器对象的功能如4.2节所描述。
4.4 The yield statement
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 5/14 页)2005-9-21 10:24:23
第四章 迭代器
4.4 yield语句
The yield statement is used in an iterator block to yield a value to the enumerator object or to signal the end of the
iteration.
迭代器块中的yield语句用于生成一个值,或发出一个迭代完成的信号。
embedded-statement:
...
yield-statement
yield-statement:
yield return expression ;
yield break ;
内嵌语句:
...
yield语句
yield语句:
yield return 表达式 ;
yield break ;
To ensure compatibility with existing programs, yield is not a reserved word, and yield has special meaning only when
it is used immediately before a return or break keyword. In other contexts, yield can be used as an identifier.
为了保证和现有程序的兼容性,yield并不是一个保留字,只有当一个return语句紧随其后时,yield语句
才有这特殊的意义。其它情况下,yield语句可以用作标识符。
The are several restrictions on where a yield statement can appear, as described in the following.
yield语句的出现首很多限制,如下所描述:
? It is a compile-time error for a yield statement (of either form) to appear outside a method-body, operator-body or
accessor-body
? It is a compile-time error for a yield statement (of either form) to appear inside an anonymous method.
? It is a compile-time error for a yield statement (of either form) to appear in the finally clause of a try statement.
? It is a compile-time error for a yield return statement to appear anywhere in a try statement that contains catch
clauses.
如果一个yield语句出现在方法体、运算符体或访问器体之外,则会引起编译错误。
如果一个yield语句出现在匿名方法内部,则会引起编译错误。
如果一个yield语句出现在finally或一个try块内,则会引起编译错误。
如果一个yield语句出现在一个带有catch语句的try块内,则会引起编译错误。
The following example shows some valid and invalid uses of yield statements.
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 6/14 页)2005-9-21 10:24:23
第四章 迭代器
下面的例子展示了一些yield语句的有效的和无效的用法。
delegate IEnumerable<int> D();
IEnumerator<int> GetEnumerator() {
try {
yield return 1; // 正确
yield break; // 正确
}
finally {
yield return 2; // 错误,yield出现在finally块中E
yield break; // 错误,yield出现在finally块中
}
try {
yield return 3; // 错误,yield return语句出现在try...catch语句中
yield break; // 正确
}
catch {
yield return 4; // 错误,yield return语句出现在try...catch语句中
yield break; // 正确
}
D d = delegate {
yield return 5; // 错误,yield语句出现在匿名方法中
};
}
int MyMethod() {
yield return 1; // 错误,迭代器块具有错误的返回值类型
}
An implicit conversion (§6.1) must exist from the type of the expression in the yield return statement to the yield
type (§22.1.3) of the iterator block.
从yield return语句中的表达式的类型到迭代器块的生成类型(见4.1.3)必存在一个隐式转换。
A yield return statement is executed as follows:
yield return语句依照下面的步骤执行:
? The expression given in the statement is evaluated, implicitly converted to the yield type, and assigned to the Current
property of the enumerator object.
? Execution of the iterator block is suspended. If the yield return statement is within one or more try blocks, the
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 7/14 页)2005-9-21 10:24:23
第四章 迭代器
associated finally blocks are not executed at this time.
? The MoveNext method of the enumerator object returns true to its caller, indicating that the enumerator object
successfully advanced to the next item.
对语句中给定的表达式进行求值,并隐式转换为生成类型,然后赋给枚举器对象的Current属性。
挂起对迭代器块的执行。如果该yield return语句位于一个或多个try块中,相应的finally块暂时不会被执
行。
MoveNext()方法向其调用者返回true,表示枚举器对象成功地前进到下一个值上。
The next call to the enumerator object’s MoveNext method resumes execution of the iterator block from where it
was last suspended.
对枚举器对象的MoveNext()方法的下一次调用将从上一次挂起的地方恢复对迭代器块的执行。
A yield break statement is executed as follows:
yield break语句依照下面的步骤执行:
? If the yield break statement is enclosed by one or more try blocks with associated finally blocks, control is initially
transferred to the finally block of the innermost try statement. When and if control reaches the end point of a finally
block, control is transferred to the finally block of the next enclosing try statement. This process is repeated until the
finally blocks of all enclosing try statements have been executed.
? Control is returned to the caller of the iterator block. This is either the MoveNext method or Dispose method of the
enumerator object.
如果yield break语句位于一个或多个带有finally块的try块中,控制将被转移到最里面的try块对应的finally块
中。当控制流程遇到finally块的结尾(如果能够的话),控制将被转移到外一层try块对应的finally块中。这
个过程持续到所有try语句对应的finally块都被执行完。
将控制返回给迭代器块的调用者。这可能从MoveNext()方法或Dispose()方法中返回。
Because a yield break statement unconditionally transfers control elsewhere, the end point of a yield break statement is
never reachable.
由于一个yield break语句无条件地将控制转移到其它地方,因此一个yield break的终点将永远不可达。
4.4.1 Definite assignment
4.4.1 明确赋值
For a yield return statement stmt of the form:
对于下面形式的yield return语句:
yield return expr ;
? A variable v has the same definite assignment state at the beginning of expr as at the beginning of stmt.
? If a variable v is definitely assigned at the end of expr, it is definitely assigned at the end point of stmt; otherwise; it is
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 8/14 页)2005-9-21 10:24:23
第四章 迭代器
not definitely assigned at the end point of stmt.
对于一个变量v,在expr的开始处和语句的开始处有同样的明确赋值。
如果一个变量v在expr的结束处被明确赋值,则它是在语句的结尾被明确赋值的;否则,它未在语句的结
尾被明确赋值。
4.5 Implementation example
4.5 实例
This section describes a possible implementation of iterators in terms of standard C# constructs. The implementation
described here is based on the same principles used by the Microsoft C# compiler, but it is by no means a mandated
implementation or the only one possible.
这一节将描述标准C#结构中的迭代器可能的实现。这里描述的实现是基于和Microsoft C#编译器相同的
原则的,但决不是唯一可能的实现。
The following Stack<T> class implements its GetEnumerator method using an iterator. The iterator enumerates the
elements of the stack in top to bottom order.
下面的Stack<T>类使用一个迭代器实现了它的GetEnumerator()方法。该迭代器按照从顶至底的顺序枚举
了堆栈中的所有元素。
using System;
using System.Collections;
using System.Collections.Generic;
class Stack<T> : IEnumerable<T> {
T[] items;
int count;
public void Push(T item) {
if (items == null) {
items = new T[4];
}
else if (items.Length == count) {
T[] newItems = new T[count * 2];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
items[count++] = item;
}
public T Pop() {
T result = items[--count];
items[count] = T.default;
return result;
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 9/14 页)2005-9-21 10:24:23
第四章 迭代器
}
public IEnumerator<T> GetEnumerator() {
for(int i = count - 1; i >= 0; --i) yield items[i];
}
}
The GetEnumerator method can be translated into an instantiation of a compiler-generated enumerator class that
encapsulates the code in the iterator block, as shown in the following.
GetEnumerator()方法可以转换为编译器自动生成的枚举器类的实例,它封装了迭代器块中指定的代码,
如下所示:
class Stack<T> : IEnumerable<T> {
...
public IEnumerator<T> GetEnumerator() {
return new __Enumerator1(this);
}
class __Enumerator1 : IEnumerator<T>, IEnumerator {
int __state;
T __current;
Stack<T> __this;
int i;
public __Enumerator1(Stack<T> __this) {
this.__this = __this;
}
public T Current {
get { return __current; }
}
object IEnumerator.Current {
get { return __current; }
}
public bool MoveNext() {
switch (__state) {
case 1: goto __state1;
case 2: goto __state2;
}
i = __this.count - 1;
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 10/14 页)2005-9-21 10:24:23
第四章 迭代器
__loop:
if(i < 0) goto __state2;
__current = __this.items[i];
__state = 1;
return true;
__state1:
--i;
goto __loop;
__state2:
__state = 2;
return false;
}
public void Dispose() {
__state = 2;
}
void IEnumerator.Reset() {
throw new NotSupportedException();
}
}
}
In the preceding translation, the code in the iterator block is turned into a state machine and placed in the MoveNext
method of the enumerator class. Furthermore, the local variable i is turned into a field in the enumerator object so it
can continue to exist across invocations of MoveNext.
上面的转换中,迭代器块中的代码被转换为状态机并放在枚举器类的MoveNext()方法中。此外,局部变
量i被转换为枚举器对象的域,因此在对MoveNext()方法的调用过程中它将一直存在。
The following example prints a simple multiplication table of the integers 1 through 10. The FromTo method in the
example returns an enumerable object and is implemented using an iterator.
下面的例子打印了整数1至10的一个简单的乘法表。例子中的FromTo()方法返回了一个用迭代器实现的
可枚举对象。
using System;
using System.Collections.Generic;
class Test {
static IEnumerable<int> FromTo(int from, int to) {
while(from <= to) yield return from++;
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 11/14 页)2005-9-21 10:24:23
第四章 迭代器
}
static void Main() {
IEnumerable<int> e = FromTo(1, 10);
foreach(int x in e) {
foreach(int y in e) {
Console.Write("{0,3} ", x * y);
}
Console.WriteLine();
}
}
}
The FromTo method can be translated into an instantiation of a compiler-generated enumerable class that
encapsulates the code in the iterator block, as shown in the following.
FromTo()方法可以被转换为由编译器自动生成的可枚举类的实例,它封装了迭代器块中的代码,如下
所示:
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
class Test {
...
static IEnumerable<int> FromTo(int from, int to) {
return new __Enumerable1(from, to);
}
class __Enumerable1 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator {
int __state;
int __current;
int __from;
int from;
int to;
int i;
public __Enumerable1(int __from, int to) {
this.__from = __from;
this.to = to;
}
public IEnumerator<int> GetEnumerator() {
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 12/14 页)2005-9-21 10:24:23
第四章 迭代器
__Enumerable1 result = this;
if(Interlocked.CompareExchange(ref __state, 1, 0) != 0) {
result = new __Enumerable1(__from, to);
result.__state = 1;
}
result.from = result.__from;
return result;
}
IEnumerator IEnumerable.GetEnumerator() {
return (IEnumerator)GetEnumerator();
}
public int Current {
get { return __current; }
}
object IEnumerator.Current {
get { return __current; }
}
public bool MoveNext() {
switch (__state) {
case 1:
if(from > to) goto case 2;
__current = from++;
__state = 1;
return true;
case 2:
__state = 2;
return false;
default:
throw new InvalidOperationException();
}
}
public void Dispose() {
__state = 2;
}
void IEnumerator.Reset() {
throw new NotSupportedException();
}
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 13/14 页)2005-9-21 10:24:23
第四章 迭代器
}
}
The enumerable class implements both the enumerable interfaces and the enumerator interfaces, enabling it to serve as
both an enumerable and an enumerator. The first time the GetEnumerator method is invoked, the enumerable object
itself is returned. Subsequent invocations of the enumerable object’s GetEnumerator, if any, return a copy of the
enumerable object. Thus, each returned enumerator has its own state and changes in one enumerator will not affect
another. The Interlocked.CompareExchange method is used to ensure thread-safe operation.
这个可枚举类同时实现了可枚举接口和枚举器接口,因此它既是可枚举的又是一个枚举器。当
GetEnumerator()方法第一次被调用时,将返回可枚举对象本身。以后对GetEnumerator()方法的调用(如果
有的话),将返回可枚举对象的一个拷贝。因此返回的每一个枚举器具有其自己的状态,一个枚举器的
改变不会影响到其它的枚举器。Interlocked.CoompareExchange()方法可以用于确保线程安全。
The from and to parameters are turned into fields in the enumerable class. Because from is modified in the iterator
block, an additional __from field is introduced to hold the initial value given to from in each enumerator.
The MoveNext method throws an InvalidOperationException if it is called when __state is 0. This protects against use
of the enumerable object as an enumerator object without first calling GetEnumerator.
from和to参数被转换为可枚举类的域。因为迭代器块改变了from,因此引入了一个附加的__from域来保
存每个枚举器中的from的初始值。
当__state是0时,MoveNext()方法将跑出一个InvalidOperationException异常。这将保证不会发生没有首先
调用GetEnumerator()方法而直接将可枚举对象用作枚举器。
file:///E|/mybook/C#2.0/C#2.0新特性/4.htm(第 14/14 页)2005-9-21 10:24:23
第一章
第五章 不完全类型
5.1 不完全声明
在定义一个分为多个部分的类型时,要使用一个新的类型修饰符——partial。为了保证和现有代码的
兼容性,这个标识符和其他标识符不同:与get和set相同,它不是一个关键字,而且它必须直接出现在关
键字class、struct和interface之一的前面。
class-declaration:
attributesopt class-modifiersopt partialopt class identifier type-parameter-listopt
class-baseopt type-parameter-constraints-clausesopt class-body ;opt
struct-declaration:
attributesopt struct-modifiersopt partialopt struct identifier type-parameter-listopt
struct-interfacesopt type-parameter-constraints-clausesopt struct-body ;opt
interface-declaration:
attributesopt interface-modifiersopt partialopt interface identifier type-parameter-listopt
interface-baseopt type-parameter-constraints-clausesopt interface-body ;opt
类声明:
特性可选 类修饰符可选 partial可选 class 标识符 类型参数列表可选
基类可选 类型参数约束条款可选 类体 ;可选
结构声明:
特性可选 结构修饰符可选 partial可选 struct 标识符 类型参数列表可选
结构接口可选 类型参数约束条款可选 结构体 ;可选
接口声明:
特性可选 接口修饰符可选 partial可选 interface 标识符 类型参数列表可选
基接口可选 类型参数约束条款可选 接口体 ;可选
不完全类型声明中的每一部分必须包含partial修饰符,并且必须和其他部分位于相同的命名空间中。
partial修饰符表明在其他位置可能有该类型声明的附加部分,但这些附加部分不是必须的,这就运行一个
单独的类型声明包含partial修饰符。
不完全类型的所有部分必须放在一起编译,这才能使这些部分在编译期间合并。但是部分类型不允许
扩展已经编译过的类型。
嵌套类型可以通过使用partial修饰符声明为多个部分。典型的情况是,包含嵌套类型的类型的声明也
使用了partial,而潜逃类型的各个部分分别声明在这个包含类型的各个不同部分中。
The partial modifier is not permitted on delegate or enum declarations.
partial修饰符不允许用于委托或枚举声明。
5.1.1 特性(Attribute)
不完全类型的各个部分上的特性将被按照不确定的顺序合并,如果一个特性被放在多个部分上,在相
当于在类型上对一个特性使用了多次。例如,下面的两部分:
[Attr1, Attr2("hello")]
partial class A {}
[Attr3, Attr2("goodbye")]
partial class A {}
相当于下面的声明:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}
类型参数上的特性按照同样的方式合并。
5.1.2 修饰符
当一个不完全类型声明中包含可访问性说明(public、protected、internal和private修饰符)时,所有其
file:///E|/mybook/C#2.0/C#2.0新特性/5.htm(第 1/4 页)2005-9-21 10:24:24
第一章
它部分都可以包含一个同样的修饰符。如果不完全类型的任何一个部分都不包含可访问性说明,这个类
型将具有默认的恰当的可访问性。
如果嵌套类型的不完全声明中包含new修饰符,在这个嵌套类型隐藏了继承的成员时将不会出现警
告。
如果类的不完全声明中的一个或多个部分包含了abstract修饰符,则这个类被认为是抽象的。否则,这
个类被认为是非抽象的。
如果类的不完全声明中的一个或多个部分包含了sealed修饰符,则这个类被认为是密封的。否则,这个
类被认为是非密封的。
注意一个类不能既是抽象的又是密封的。
当在不完全类型声明的一个部分上使用了unsafe修饰符,则只有这一个特定的部分被认为是在不安全
环境中。
5.1.3 类型参数和约束
如果一个分型类型被声明在多个部分中,每个部分都必须声明类型参数。各个部分必须具有相同数量
的类型参数,每个类型参数的名称和顺序也必须一样。
若一个不完全泛型类型包含类型参数约束(where子句),则其它部分中也可以包含同样的约束。不过
每个包含约束的部分必须对相同的类型参数的集合进行约束,这个集合中的每个类型参数的类、接口和
构造器约束必须相同。如果不完全泛型类型的每个部分均未指定类型参数约束,则这些类型参数被认为
是不受约束的。
下面的例子
partial class Dictionary<K,V>
where K: IComparable<K>
where V: IKeyProvider<K>, IPersistable
{
...
}
partial class Dictionary<K,V>
where V: IPersistable, IKeyProvider<K>
where K: IComparable<K>
{
...
}
partial class Dictionary<K,V>
{
...
}
是正确的,因为包含约束的部分(第一个和第二个)分别有效地对同一组类型参数指定了相同的一组
类、接口和构造器约束。
5.1.4 基类
当一个不完全类声明中包含指定基类时,允许各个部分包含同样的指定基类。如果一个不完全类型的
任何部分都未指定基类,则该类的基类为System.Object。
5.1.5 基接口
分别声明在不同部分中的基接口是指定在各个部分上的基接口的联合。一个特定的基接口在每个部分
上只能命名一次,但允许在多个部分上命名同一个接口。基接口中的任何成员只能实现一次。
In the example
在下面的例子中
partial class C: IA, IB {...}
partial class C: IC {...}
file:///E|/mybook/C#2.0/C#2.0新特性/5.htm(第 2/4 页)2005-9-21 10:24:24
第一章
partial class C: IA, IB {...}
C类的基接口集合是IA、IB和IC。
通常,一个部分只为该部分上声明的接口提供一个实现,然而,这不是必须的。一个部分可以为另一
个不同的部分上声明的接口提供实现:
partial class X
{
int IComparable.CompareTo(object o) {...}
}
partial class X: IComparable
{
...
}
5.1.6 成员
在多个部分中声明的成员只是每个部分中声明的成员的简单聚合。类型声明的所有部分中的类体共享
相同的声明空间,并且每个成员的作用域都贯穿所有的部分。任何成员的可访问性总是包含了封闭类型
的所有部分;在某一部分中声明的private成员可以在另一部分中自由地访问。如果在多个部分中声明了相
同的成员则会产生编译错误,除非这个成员是一个用partial修饰符声明的类型。
partial class A
{
int x; // 错误,不能多次声明x
partial class Inner // 正确,这是一个不完全内部类型
{
int y;
}
}
partial class A
{
int x; // 错误,不能多次声明x
partial class Inner // 正确,这是一个不完全内部类型
{
int z;
}
}
尽管成员的顺序对于C#代码来说并不重要,但对于和其它语言或环境进行接口连接这可能是重要的。
在这种情况下,在类型的多个部分中声明的成员的顺序是未定义的。
5.2 名字绑定
尽管可扩展的类型的各个部分必须声明在相同的命名空间中,但各个部分中可以写入不同的命名空间
声明。因此,不同的using指令可以出现在各个部分中。当在一个部分中解释简单名字时,仅考虑该部分
中声明的命名空间中的using指令。
namespace N
{
using List = System.Collections.ArrayList;
partial class A
{
List x; // x的类型是System.Collections.ArrayList
}
}
file:///E|/mybook/C#2.0/C#2.0新特性/5.htm(第 3/4 页)2005-9-21 10:24:24
第一章
namespace N
{
using List = Widgets.LinkedList;
partial class A
{
List y; // y的类型是Widgets.LinkedList
}
}
file:///E|/mybook/C#2.0/C#2.0新特性/5.htm(第 4/4 页)2005-9-21 10:24:24
C# Version 3.0 Specification
September 2005
翻译: 邱龙斌 <qiu_lb (at) hotmail.com>
2005-09-15
得益于互联网的开放性和专业人员的共享精神,过去几年里我在网络上搜索到很多重要的参考
资料和电子文档。在此对大家的奉献性的工作表示感谢。
近日无意中发现了Microsoft的LINQ项目,这个项目是用来试验C#未来版本也就是3.0版本
的新功能的。有兴趣的朋友可以到LINQ项目主页去看看,上面有C# 3.0 和LINQ的介绍、示
例代码。
http://msdn.microsoft.com/netframework/future/linq/
本人使用c++多年,深知语言核心的稳定和程序库的激进同样重要。对c++而言boost提供了许
多库扩展方面的最佳实践,比如boost.python,boost.function,boost.lambda等。新的c++0x
标准提案中提到在语言核心层直接支持concept和model的概念,从而在编译期进行concept
和model的检查,就类型约束这点,我知道c#2.0泛型是用where表示泛型类型参数的约束的。
C#语言核心,近年来动作很大,继2.0加入泛型、匿名方法、迭代器、不完整类型、Nullable
类型之后,3.0更是加入了一些引人注目的新特性。感慨之余,开发人员又要继续学习了;同
时开始担心例如Mono,DotGnu等开源.Net项目。
浏览了一下C# 3.0 Specification,感觉C#有越来越动态化的倾向,数据查询方面也更直接。
花了点时间翻译成中文,希望对有需要的朋友有用。翻译错误再所难免,有问题的朋友可以跟
我联系,讨论本文的翻译问题。
声明:本译文不可用于商业目的流传, Microsoft可能有异议。
Notice
© 2005 Microsoft Corporation. All rights reserved.
Microsoft, Windows, Visual Basic, Visual C#, and Visual C++ are either registered trademarks or trademarks of Microsoft
Corporation in the U.S.A. and/or other countries/regions.
Other product and company names mentioned herein may be the trademarks of their respective owners.
Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
Overview of C# 3.0
目录
26.C# 3.0概述.....................................................................................................................................................3
26.1隐型局部变量(implicitly typed local variable)...........................................................................................3
26.2扩展方法......................................................................................................................................................4
26.2.1声明扩展方法.......................................................................................................................................4
26.2.2导入扩展方法.......................................................................................................................................4
26.2.3扩展方法调用.......................................................................................................................................5
26.3Lambda表达式............................................................................................................................................6
26.3.1Lambda表达式转换...............................................................................................................................7
26.3.2类型推导...............................................................................................................................................8
26.3.3Overload resolution 重载决议..............................................................................................................10
26.4对象和集合初始化器................................................................................................................................10
26.4.1Object initializers 对象初始化器.........................................................................................................11
26.4.2集合初始化器.....................................................................................................................................13
26.5匿名类型....................................................................................................................................................14
26.6隐型数组(Implicitly typed arrarys).......................................................................................................15
26.7查询表达式................................................................................................................................................16
26.7.1查询表达式translation.......................................................................................................................17
26.7.1.1where子句....................................................................................................................................17
26.7.1.2select子句.....................................................................................................................................17
26.7.1.3group子句.....................................................................................................................................18
26.7.1.4orderby子句..................................................................................................................................18
26.7.1.5多重产生器..................................................................................................................................18
26.7.1.6info子句........................................................................................................................................19
26.7.2查询表达式模式.................................................................................................................................19
26.7.3正式的转换规则.................................................................................................................................20
26.8表达式树(Expression trees)..................................................................................................................22
ii Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
26.C# 3.0概述
C# 3.0 (“C# 魔兽(Orcas)”) 引入了几个构建在C# 2.0上的语言扩展,用来支持创建和使用更高级的函数
式(functional或译:泛函)类库。这些扩展允许 组合 (compositional)APIs 的构造,这些APIs与关系数据
库和XML等领域中的查询语言具有同等的表达力。
隐型局部变量 ,允许局部变量的类型从初始化它们的表达式推导而来。
扩展方法 ,使得使用附加(additional)的方法扩展已存在的类型和构造类型成为可能。
Lambda 表达式 ,是匿名方法的演进,可提供改良的类型推导和到dalegate 类型和表达式树的 转换。
对象初始化器 ,简化了对象的构造和初始化。
匿名类型 ,是从对象初始化器自动推导和创建的元组(tuple)类型。
隐型数组 ,数组创建和初始化的形式,它从数组初始化器推导出数组的元素类型。
查询表达式 ,为类似于关系型和层次化查询语言(比如 SQL 和 XQuery ) 提供一个语言集成
(intergrated)的语法。
表达式树 ,允许lambda表达式表示为数据(表达式树)而不是代码(delegate)。
本文档是这些特征的技术概述。文档引用了C#语言规范1.2(§1-§18)和C#语言规范2.0(§19-§25),
这两个规范都在C#语言主页上(
http://msdn.microsoft.com/vcsharp/language)。
26.1 隐型局部变量(implicitly typed local variable)
在隐型局部变量声明中,正被声明的局部变量的类型从初始化这个变量的表达式推导得来。当局部变
量声明指明var作为类型,并且该范围域(scope)中没有var名称的类型存在,这个声明就称为隐型局部
声明。例如:
var i = 5;
var s = "Hello";
var d = 1.0;
var numbers = new int[] {1, 2, 3};
var orders = new Dictionary<int,Order>();
上面的隐型局部变量声明精确地等同于下面的显型(explicitly typed)声明:
int i = 5;
string s = "Hello";
double d = 1.0;
int[] numbers = new int[] {1, 2, 3};
Dictionary<int,Order> orders = new Dictionary<int,Order>();
隐型局部变量声明中的局部变量声明符(declarator)遵从下面这些约束:
声明符必须包含初始化器。
初始化器必须是一个表达式。初始化器不能是一个自身的对象或者集合初始化器(§)),但是它可以
是包含一个对象或集合初始化器的一个new表达式。
初始化器表达式的编译期类型不可以是空(null)类型。
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 3
Overview of C# 3.0
如果局部变量声明包含了多个声明符,这些声明符必须具备同样的编译期类型。
下面是一些不正确的隐型局部变量声明的例子:
var x; // Error, no initializer to infer type from
var y = {1, 2, 3}; // Error, collection initializer not permitted
var z = null; // Error, null type not permitted
因为向后兼容的原因,当局部变量声明指定var作为类型,而范围域中又存在叫var的类型,则这个声
明会推导为那个叫var的类型;然后,会产生一个关注含糊性(ambiguity)的警告,因为叫var的类型违
反了既定的类名首字母大写的约定,这个情形也未必会出现。(译者:视编译器实现而定)
for表达式(§8.8.3)的for 初始化器 (for-initializer) 和using表达式的资源获取(resource-acquisition)可以作为
一个隐型局部变量声明。同样,foreach表达式(§8.8.4)的迭代变量可以声明为一个隐型局部变量,这种
情况下,(隐型局部变量的)类型推导为正被枚举(enumerated)的集合的元素的类型。例子:
int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);
n的类型推导为numbers的元素类型int。
26.2 扩展方法
扩展方法 是可以通过使用实例方法语法调用的静态方法。效果上,扩展方法使得用附加的方法扩展已
存在类型和构造类型成为可能。
注意
扩展方法不容易被发现并且在功能上比实例方法更受限。由于这些原因,推荐保守地使用和仅在实例方法不可行
或不可能的情况下使用。
其它种类的扩展方法,比如属性、事件和操作符,正在被考虑当中,但是当前并不被支持。
26.2.1 声明扩展方法
扩展方法是通过指定关键字 this 修饰方法的第一个参数 而声明的。扩展方法仅可声明在静态类中。下面
是声明了两个扩展方法的静态类的例子:
namespace Acme.Utilities
{
public static class Extensions
{
public static int ToInt32(this string s) {
return Int32.Parse(s);
}
public static T[] Slice<T>(this T[] source, int index, int count) {
if (index < 0 || count < 0 || source.Length – index < count)
throw new ArgumentException();
T[] result = new T[count];
Array.Copy(source, index, result, 0, count);
return result;
}
}
}
扩展方法具备所有常规静态方法的所有能力。另外,一旦被导入,扩展方法可以使用实例方法语法调
用之。
26.2.2 导入扩展方法
扩展方法用using-namespace-directives (§9.3.2)导入。除了导入包含在名字空间中的类型外,usingnamespace-
directives 也导入了名字空间中所有静态类中的所有扩展方法。实际上,被导入的扩展方法作
4 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
为被修饰的第一个参数类型上的附加方法出现,并且相比常规实例方法具有较低的优先权。比如,当
使用using-namespace-directive导入上个例子中Acme.Utilities 名字空间:
using Acme.Utilities;
它使得可以在静态类Extension上使用实例方法语法调用扩展方法:
string s = "1234";
int i = s.ToInt32(); // Same as Extensions.ToInt32(s)
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] a = digits.Slice(4, 3); // Same as Extensions.Slice(digits, 4, 3)
26.2.3 扩展方法调用
扩展方法调用的详细规则表述如下。以如下调用形式之一:
expr . identifier ( )
expr . identifier ( args )
expr . identifier < typeargs > ( )
expr . identifier < typeargs > ( args )
如果调用的正常处理过程发现没有适用的实例方法(特别地,如果这个调用的候选方法集是空的),
就会试图处理扩展方法调用的构造。方法调用会首先被分别重写称如下之一:
identifier ( expr )
identifier ( expr , args )
identifier < typeargs > ( expr )
identifier < typeargs > ( expr , args )
重写后的形式然后被作为静态方法调用处理,除非标识符identifier决议为:以最靠近的封闭名字空间
声明开始,以每个封闭名字空间声明继续,并以包含的编译单元结束,持续地试图用组成所有可访问
的,由 using-namespace-directives 导入的,指明为 identifier 名字的扩展方法 处理重写的方法调用。第一
个产生非空候选方法集的方法组(method group)就成为被选中的重写的方法调用。如果所有的努力都只
产生空的候选集,则发生编译期错误。
前面的规则标表明实例方法优先于扩展方法,并且导入进内层名字空间中的扩展方法优先于导入进外
层名字空间中的扩展方法。例如:
using N1;
namespace N1
{
public static class E
{
public static void F(this object obj, int i) { }
public static void F(this object obj, string s) { }
}
}
class A { }
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 5
Overview of C# 3.0
class B
{
public void F(int i) { }
}
class C
{
public void F(object obj) { }
}
class X
{
static void Test(A a, B b, C c) {
a.F(1); // E.F(object, int)
a.F("hello"); // E.F(object, string)
b.F(1); // B.F(int)
b.F("hello"); // E.F(object, string)
c.F(1); // C.F(object)
c.F("hello"); // C.F(object)
}
}
例子中,B的方法优先于第一个扩展方法,C的方法优先于两个扩展方法。
26.3 Lambda表达式
C# 2.0 引入了匿名方法,它允许在delegate值(delegate value) (译者:delegate对象)被需要的地方以内联
(in-line)方式写一个代码块。当匿名方法提供了大量函数式编程语言(或泛函编程)(functional
programming)的表达力时,实质上,匿名方法的语法是相当烦琐和带有强制性的。Lambda表达式提供
了一个更加简练的函数式语法来写匿名方法。
Lambda表达式写成一个后面紧跟 => 标记的参数列表,=>之后是一个表达式或表语句块。
expression:
assignment
non-assignment-expression
non-assignment-expression:
conditional-expression
lambda-expression
query-expression
lambda-expression:
( lambda-parameter-listopt ) => lambda-expression-body
implicitly-typed-lambda-parameter => lambda-expression-body
lambda-parameter-list:
explicitly-typed-lambda-parameter-list
implicitly-typed-lambda-parameter-list
explicitly-typed-lambda-parameter-list
explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter-list , explicitly-typed-lambda-parameter
explicitly-typed-lambda-parameter:
parameter-modifieropt type identifier
6 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
implicitly-typed-lambda-parameter-list
implicitly-typed-lambda-parameter
implicitly-typed-lambda-parameter-list , implicitly-typed-lambda-parameter
implicitly-typed-lambda-parameter:
identifier
lambda-expression-body:
expression
block
Lambda表达式的参数可以是显型和隐型的。在显型参数列表中,每个参数的类型是显式指定的。在隐
型参数列表中,参数的类型由lambda表达式出现的语境推导——特定地,当lambda表达式被转型到一
个兼容的delegate类型时,delegate类型提供参数的类型(§)。
在有单一的隐型参数的lambda表达式中,圆括号可以从参数列表中省略。换句话说,如下形式的
lambda表达式
( param ) => expr
可以被简写成
param => expr
下面是一些lambda表达式的例子:
x => x + 1 // Implicitly typed, expression body
x => { return x + 1; } // Implicitly typed, statement body
(int x) => x + 1 // Explicitly typed, expression body
(int x) => { return x + 1; } // Explicitly typed, statement body
(x, y) => x * y // Multiple parameters
() => Console.WriteLine() // No parameters
通常,C# 2.0规范§21中提供的匿名方法规范,也应用上了lambda表达式。Lambda表达式是匿名方法
的泛函超集,它提供了如下附加功能:
Lambda表达式允许参数类型被省略掉和被推导,尽管匿名方法要求显式指定参数类型。
Lambda表达式体可以是一个表达式或者语句块,尽管匿名方法体可以是一个语句块。
Lambda表达式作为参数传递参与类型参数推导(§26.3.3)和重载决议。
带有表达式体的Lambda表达式可以被转换成表达式树(§26.8)。
注意
PDC 2005技术预览编译器不支持带有语句体的lambda表达式。在需要语句体的情况下,必须使用C# 2.0匿名方
法语法。
26.3.1 Lambda表达式转换
与匿名方法表达式(anonymous-method-expression)类似,lambda表达式是用特殊转换规则作为值(value)
类型分类的。这个值(value)没有类型,但是可以隐式转型至一个兼容的delegate类型。特别地,delegate
类型D与lambda表达式L兼容的,如果:
D和L有相同数目的参数。
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 7
Overview of C# 3.0
如果L有显型参数列表,D中的每个参数有着与相应的L中的参数相同的类型和修饰符。
如果L有隐型参数列表,D不可有ref或out参数。
如果D有void 返回类型,并且L的体(body)是一个表达式,当L的每个参数被给定为对应的D 中参
数的类型时,L的体是一个允许作为语句-表达式(statement-expression(§8.6))的有效表达式
如果D有void返回类型并且L的体是语句块,当L的每个参数类型是被给定为相应的D参数的类
型时,L的体是一个没有返回语句的有效语句块。
如果D有non-void返回值并且L的体是一个表达式,当L的每个参数类型是被给定的相应于D参数
的类型时,L的体是一个可以隐式转换到D返回类型的有效表达式。
如果D有non-void返回值并且L的体是一个语句块,当L的每个参数类型是被给定的相应于D参数
的类型时,L的体是一个有效的语句块,语句块中有不可到达 (non-reachable) 的终点 (end point )(译者:
是否应该为“没有不可到达的终点”),且每个终点的返回语句指明一个可以隐式转换到 D 返回类
型的表达式。
下面的例子使用泛型delegagte类型Func<A,R>表示一个带有参数类型A和返回类型R的函数:
delegate R Func<A,R>(A arg);
赋值如下:
Func<int,int> f1 = x => x + 1; // Ok
Func<int,double> f2 = x => x + 1; // Ok
Func<double,int> f3 = x => x + 1; // Error
每个Lambda表达式的参数和返回类型决定于lambda表达式被赋值的变量的类型。第一个赋值成功地
转换lambda表达式到delegate类型Func<int,int>,是因为当 x是int型,x+1 是一个有效的表达式并可
以隐式地转换到类型int。同样第二个赋值成功地转换lambda表达式到delegate类型Func<int,double>,
是因为x+1的返回值(类型int)是隐式转换成double的。然而第三个赋值有编译期错误,因为当x是
double,x+1是double,不能够隐式转变到类型int。
26.3.2 类型推导
当泛型方法被调用而不指明类型参数时,参数推导过程试图从调用中推导出类型参数。Lambda表达式
参数传递给泛型方法参与这个类型推导过程。
如同§20.6.4中表述的那样,类型推导首先为每个参数独立的发生。在初始阶段,不能从lambda表达式
参数推导出任何东西。然而,初始阶段之后,产生了使用迭代过程的额外的推导。特别地,只要有一
个或多个满足如下条件为真的参数存在,推导将会产生:
参数是lambda表达式,下面称为L,从中,尚无推导。
相应的参数类型,下面称为P,是有返回类型的含有一个或多个方法类型参数的delegate。
P和L拥有相同数目的参数,并且P中的每个参数与L中相应的参数具有相同的修饰符,或者如果L有隐型参
数列表时,没有修饰符。
P的参数类型不包含方法类型参数或者包含仅仅一个方法类型参数,对这个参数已经产生一个相容的推导集。
如果L有一个显型参数列表,当推导出的类型对于P中的方法类型参数是可替换的时候,P中的每
个参数拥有与L中对应的参数相同的类型。
如果L有一个隐型参数列表,当推导出的类型对于P中的方法类型参数是可替代的,并且返回参数
类型被给予L的参数,L的体是一个有效表达式或语句块。
8 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
返回类型可以为L推导出来,描述如下:
对每一个这样的参数,将会通过关联P的返回类型和L的推导返回类型做出推论,并且新的推论被加入
进累积的推论集。这个过程将重复进行,直到没有更进一步的推论产生为止。
因为类型推导和重载决议的原因,lambda表达式L推导出的类型决定于下面:
如果L的体是一个表达式,表达式的类型就是推导出的L的返回类型。
如果L的体是一个语句块,如果由语句块中 return 语句表达式的类型 形成的集合(set)正好包含一个
集合中每个类型都可隐式转换成的类型,那么这个类型就是推导出的L的返回类型。(译者:如果有
个集合{int, byte, double},则double满足要求)
此外,返回类型不能为L推导出来。
作为一个包含lambda表达式的类型推导的例子,考虑声明于System.Query.Sequence类中的Select扩展
方法:
namespace System.Query
{
public static class Sequence
{
public static IEnumerable<S> Select<T,S>(
this IEnumerable<T> source,
Func<T,S> selector)
{
foreach (T element in source) yield return selector(element);
}
}
}
假定System.Query名字空间使用using子句导入,并且给出一个类Customer,带有类型为string的属性
Name, Select方法可用作选择一列 (list of )customers 的 名字
List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);
Select扩展方法调用通过重写静态方法调用处理:
IEnumerable<string> names = Sequence.Select(customers, c => c.Name);
因为类型参数未被显式指明,将会使用类型推导来推导类型参数。首先customers参数被关联到source
参数,推导T是Customer。然后使用前面描述的lambda表达式类型推导过程, c是给定类型Customer,
而表达式c.Name被关联到selector参数的返回类型上,推导s是string,这样,调用就等价于
Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)
返回类型是IEnumerable<string>。
下面的例子示范了lambda表达式类型推导是如何允许类型信息在泛型函数调用的参数之间“流动”的。
给出方法
static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) {
return f2(f1(value));
}
调用的类型推导
double seconds = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalSeconds);
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 9
Overview of C# 3.0
处理过程如下:首先参数”1:15:30”被关联到值参数,推导X是string。然后第一个lambda表达式的参
数s是给定推导类型string,并且表达式TimeSpan.Parse(s)被关联到f1的返回类型上,推导Y为
System.TimeSpan。最后第二个lambda表达式的参数t,是给定为推导类型System.TimeSpan,表达式
t.ToTalSeconds被关联到f2的返回类型上,推导Z为double。这样,调用的返回类型就是double。
26.3.3 Overload resolution 重载决议
参数列表中的Lambda表达式在某些条件下影响重载决议。
下面的规则要增加进§7.4.2.3:给定一个lambda表达式L,为其推导出的返回类型存在,如果delegate
类型D1 和D2具有相同的参数列表,从L 到 D 1 的隐式转型比从 L 到D2的隐式转型更好;并且从L 推导出
的返回类型到D1返回类型的隐式转型比从L推导出的返回类型到D2返回类型的隐式转型更好。如果这
些条件不为真,两者都不行。
下面的例子例示了这个规则的效果。
class ItemList<T>: List<T>
{
public int Sum<T>(Func<T,int> selector) {
int sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}
public double Sum<T>(Func<T,double> selector) {
double sum = 0;
foreach (T item in this) sum += selector(item);
return sum;
}
}
ItemList<T>类有两个Sum方法。每个方法都有一个selector参数,方法从列表项中提取值累加进sum。
提取的值可以是int或double型,返回sum同样可以是int或double型。
Sum方法可以作为例子用于从detail列表中依次计算和。
class Detail
{
public int UnitCount;
public double UnitPrice;
...
}
void ComputeSums() {
ItemList<Detail> orderDetails = GetOrderDetails(...);
int totalUnits = orderDetails.Sum(d => d.UnitCount);
double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
...
}
orderDetails.Sum首次调用中,两个Sum方法都适用,这是因为lambda表达式d=>d.UnitCount兼容于
Func<Detail,int>和Func<Detail,double>这两者。然而,重载决议选择了第一个Sum方法,这是因为转换
至Func<Detail,in>好于转换至Func<Detail,double>。
orderDetails.Sum的第二次调用中,仅仅第二个Sum方法适用,这是因为lambda表达式
d=>d.UnitPrice*d.UnitCount产生的类型是double。因此重载决议为此调用选择了第二个方法。
26.4 对象和集合初始化器
对象创建表达式(§7.5.10.1)可以包含一个对象或集合初始化器,用于初始化新创建的对象的成员或新创
建的集合的元素。
10 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
object-creation-expression:
new type ( argument-listopt ) object-or-collection-initializeropt
new type object-or-collection-initializer
object-or-collection-initializer:
object-initializer
collection-initializer
对象创建表达式可以省略构造函数(译者:或译“构造器”)(constructor)的参数列表和封闭的圆括号,而
提供给它一个对象或集合初始化器。省略构造函数参数列表和封闭的圆括号等价于指定一个空参数列
表。
包含对象或集合初始化器的对象创建表达式的执行包含首先调用实例构造函数,然后执行由对象或集
合初始化器指定的成员或元素初始化动作。
对象或集合初始化器不能引用正被实例化的对象实例。
26.4.1 Object initializers 对象初始化器
对象初始化器指定一个或多个对象的域或属性的 值。
object-initializer:
{ member-initializer-listopt }
{ member-initializer-list , }
member-initializer-list:
member-initializer
member-initializer-list , member-initializer
member-initializer:
identifier = initializer-value
initializer-value:
expression
object-or-collection-initializer
对象初始化器由一系列成员初始化器组成,封闭于{和}标记内并且由逗号间隔。每个成员初始化器必须
指出正被初始化的对象的域或属性的名字,后面是等号”=”和 表达式或者 对象或集合的 初始化器 。
在等号后面指定表达式的成员初始化器作为与对域或属性赋值同样的方式处理。
在等号后指定一个对象初始化器的成员初始化器是对内嵌对象的初始化。对象初始化器中的赋值作为
域或属性成员的赋值对待,而不是给域或属性赋予新值。值类型的属性不可用这种构造方式初始化。
在等号后指定集合初始化器的成员初始化器是对内嵌集合的初始化。初始化器中给定的元素被加进域或
属性引用的集合中,而不是给域或属性赋予新的集合。域或属性必须是满足§中指定要求的集合类型。
下面的类要求一个有两个坐标的point:
public class Point
{
int x, y;
public int X { get { return x; } set { x = value; } }
public int Y { get { return y; } set { y = value; } }
}
Point的实例可以被创建和实例化如下:
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 11
Overview of C# 3.0
var a = new Point { X = 0, Y = 1 };
它等效于
var a = new Point();
a.X = 0;
a.Y = 1;
下面的这个类表示由两个points构成的rectangle。
public class Rectangle
{
Point p1, p2;
public Point P1 { get { return p1; } set { p1 = value; } }
public Point P2 { get { return p2; } set { p2 = value; } }
}
Rectangle的实例可以被创建和初始化如下:
var r = new Rectangle {
P1 = new Point { X = 0, Y = 1 },
P2 = new Point { X = 2, Y = 3 }
};
它等效于
var r = new Rectangle();
var __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
r.P1 = __p1;
var __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
r.P2 = __p2;
这里__p1和__p2是临时变量且是不可见和不可访问的。
如果Rectangle的构造函数分配了两个内嵌的Point的实例
public class Rectangle
{
Point p1 = new Point();
Point p2 = new Point();
public Point P1 { get { return p1; } }
public Point P2 { get { return p2; } }
}
下面的构造可被用于初始化内嵌的Point实例,而不是赋予新的实例值。
var r = new Rectangle {
P1 = { X = 0, Y = 1 },
P2 = { X = 2, Y = 3 }
};
它等效于
var r = new Rectangle();
r.P1.X = 0;
r.P1.Y = 1;
r.P2.X = 2;
r.P2.Y = 3;
12 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
26.4.2 集合初始化器
集合初始化器指定集合的元素。
collection-initializer:
{ element-initializer-listopt }
{ element-initializer-list , }
element-initializer-list:
element-initializer
element-initializer-list , element-initializer
element-initializer:
non-assignment-expression
集合初始化器由一系列元素初始化器组成,封闭进 { 和 } 标记内,以逗号间隔。每个元素初始化器指
定一个将被加进正被初始化的集合对象中的元素。
下面是对象创建表达式的例子,包含有一个集合初始化器:
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
被应用了集合初始化器的集合对象必须是实现了正好一个类型 T 的
System.Collections.Generic.IConlection<T> 的 类型。此外必须存在从每个元素类型到T类型的隐式转型。
如果这些条件都不满足,就产生编译期错误。集合初始化器对每个指定元素依次调用
ICollection<T>.Add(T)方法。
下面的类表示一个名字和电话号码列表的contact。
public class Contact
{
string name;
List<string> phoneNumbers = new List<string>();
public string Name { get { return name; } set { name = value; } }
public List<string> PhoneNumbers { get { return phoneNumbers; } }
}
List<Contact>可以被创建和实例化如下:
var contacts = new List<Contact> {
new Contact {
Name = "Chris Smith",
PhoneNumbers = { "206-555-0101", "425-882-8080" }
},
new Contact {
Name = "Bob Harris",
PhoneNumbers = { "650-555-0199" }
}
};
它等效于:
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 13
Overview of C# 3.0
var contacts = new List<Contact>();
var __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
contacts.Add(__c1);
var __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
contacts.Add(__c2);
这里__c1和__c2是临时变量,不可见,也不可访问。
26.5 匿名类型
C# 3.0允许new操作符与匿名对象初始化器联用来创建一个匿名类型的对象。
primary-no-array-creation-expression:
…
anonymous-object-creation-expression
anonymous-object-creation-expression:
new anonymous-object-initializer
anonymous-object-initializer:
{ member-declarator-listopt }
{ member-declarator-list , }
member-declarator-list:
member-declarator
member-declarator-list , member-declarator
member-declarator:
simple-name
member-access
identifier = expression
匿名对象初始化器声明一个匿名类型并返回这个类型的实例。一个匿名类型是一个无名类(nameless
class)(译者:参考jjhou先生的翻译“具名(named)”),它直接继承自Object。匿名类型的成员是一系
列推导自用于创建这个类型实例的对象初始化器的读/写属性。特别地,匿名对象初始化器具有如下形
式:
new { p1 = e1 , p2 = e2 , … pn = en }
它声明了一个如下形式的匿名类型
class __Anonymous1
{
private T1 f1 ;
private T2 f2 ;
…
private Tn fn ;
public T1 p1 { get { return f1 ; } set { f1 = value ; } }
public T2 p2 { get { return f2 ; } set { f2 = value ; } }
…
public T1 p1 { get { return f1 ; } set { f1 = value ; } }
}
这里每个Tx是对应表达式ex的类型。匿名对象初始化器中的表达式是null类型是一个编译期错误。
14 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
匿名类型的名字是由编译器自动产生的,在程序正文中不可被引用。
在同样的程序中,以相同顺序指定了一系列相同名字和类型的两个匿名对象初始化器将会产生相同匿
名类型的实例。(这个定义包含了属性的次序,是因为它在某些环境中这是可观测和重要的,比如
reflection)
例子
var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;
最后一行的赋值是可行的,因为p1和 p2具有相同的匿名类型。
成员声明符可以缩写成简单的名字(§7.5.2)或一个成员访问(§7.5.4)。这称为投射初始化器(projection
initializer),是具备相同名字属性声明和赋值的速记方式。
identifier expr . identifier
分别等价于下面:
identifer = identifier identifier = expr . identifier
因此,在投射初始化器中,identifier选择了被赋予值的值和域或属性。直观上,投射初始化器不仅投射
值,也投射值的名字。
26.6 隐型数组(Implicitly typed arrarys)
扩展 数组创建表达式(§7.5.10.2)的语法用以支持隐型数组创建表达式:
array-creation-expression:
…
new [ ] array-initializer
在隐型数组创建表达式中,数组实例的类型推导自数组初始化器中元素的类型。特别地,数组初始化
器中表达式类型形成的类型集合(set),必须包含一个这样的类型,其中每个类型都可隐式转型成它,并
且这个类型不是null类型,如此,这个类型的数组就被创建了。如果不能推导出一个准确的类型,或者
推导出的类型是空null类型,编译器错误就会出现。
下面是隐型数组创建表达式的例子:
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { 1, 1.5, 2, 2.5 }; // double[]
var c = new[] { "hello", null, "world” }; // string[]
var d = new[] { 1, "one", 2, "two" }; // Error
最后一个表达式导致编译器错误,这是因为int和string都不能隐式转换成对方。显型数组创建表达式
必须这么使用:例如指定类型为object[]。另外,其中一个元素可以被转型到一个公共基类型,这个类
型就会成为推导出的元素类型。
隐型数组创建表达式可以与匿名对象初始化器结合使用来创建匿名类型的数据结构。例如:
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 15
Overview of C# 3.0
var contacts = new[] {
new {
Name = "Chris Smith",
PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
},
new {
Name = "Bob Harris",
PhoneNumbers = new[] { "650-555-0199" }
}
};
26.7 查询表达式
查询表达式为查询提供了一个类似于关系和分层的查询语言 ( 如 SQL 和 XQuery) 的 语言集成(译者:
intergrated或译“整合”)语法。
query-expression:
from-clause query-body
from-clause:
from from-generators
from-generators:
from-generator
from-generators , from-generator
from-generator:
identifier in expression
query-body:
from-or-where-clausesopt orderby-clauseopt select-or-group-clause into-clauseopt
from-or-where-clauses:
from-or-where-clause
from-or-where-clauses from-or-where-clause
from-or-where-clause:
from-clause
where-clause
where-clause:
where boolean-expression
orderby-clause:
orderby ordering-clauses
ordering-clauses:
ordering-clause
ordering-clauses , ordering-clause
ordering-clause:
expression ordering-directionopt
ordering-direction:
ascending
descending
16 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
select-or-group-clause:
select-clause
group-clause
select-clause:
select expression
group-clause:
group expression by expression
into-clause:
into identifier query-body
查询表达式作为非赋值(non-assignment-expression)表达式分类,其定义出现在§。
查询表达式以from开始,结束于select或group子句。开头的from子句可以跟随0个或者更多个from
或where子句。每个from子句都是一个产生器,它引入了一个迭代变量在序列上搜索;每个where子
句是一个过滤器,它从结果中排除一些项。最后的select或group子句指定了依据迭代变量得出的结果
的外形(shape)。Select或group子句前面可有一个orderby子句,它指明返回结果的顺序。最后into子句
可以通过把一条查询语句的结果作为产生器插进子序列查询中的方式来拼接查询。
在查询表达式中,多个产生器的from子句正好等价于多个连续的带有单个产生器的 from 子句。
26.7.1 查询表达式translation
C# 3.0语言没有指定查询表达式准确的执行语义。然而C# 3.0 把查询表达式转换(translate)成遵循查询
表达式模式的多个方法的调用。特别地,查询表达式被转换成名为Where, Select, SelectMany, OrderBy,
OrderByDescending, ThenBy, ThenByDescending和GroupBy的方法调用,这些方法预期拥有特别的签名
和返回类型,描述于§。这些方法可以是被查询对象的实例方法或者对象外部的扩展方法,它们实现了
实际上的查询的执行过程。
从查询表达式到方法调用的转换(translation),是发生在任何类型绑定或重载决议执行之前的语法映射。
转换要求保证语法上的正确,但不保证产生语义正确的C#代码。查询表达式的转换之后,产生的方法
调用作为常规函数调用被处理,并且这可能依次暴露出错误。比如如果方法不存在,再比如参数类型
错误或者方法是泛型的而类型推导失败。
查询表达式的转换通过一系列例子示范如下。正式的转换规则的描述在后面部分。
26.7.1.1 where子句
查询表达式中的where子句:
from c in customers
where c.City == "London"
select c
转换成带有通过结合迭代变量标识符和 where 子句表达式合成的 lambda 表达式 的Where方法。
customers.
Where(c => c.City == "London")
26.7.1.2 select子句
前面部分的例子示范了选择最内层迭代变量的 select 子句是如何通过转换成方法调用而被移除的。
Select子句选择最内层迭代变量以外的东西:
from c in customers
where c.City == "London"
select c.Name
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 17
Overview of C# 3.0
转换成合成的lambda表达式的Select方法的调用:
customers.
Where(c => c.City == "London").
Select(c => c.Name)
26.7.1.3 group子句
group子句:
from c in customers
group c.Name by c.Country
转换成GroupBy方法的调用
customers.
GroupBy(c => c.Country, c => c.Name)
26.7.1.4 orderby子句
orderby子句:
from c in customers
orderby c.Name
select new { c.Name, c.Phone }
转换成OrderBy方法的调用,或者如果递减方向被指定时,转换成OrderByDescending方法的调用。
customers.
OrderBy(c => c.Name).
Select(c => new { c.Name, c.Phone })
orderby子句中的第二个(secondary)次序
from c in customers
orderby c.Country, c.Balance descending
select new { c.Name, c.Country, c.Balance }
转换成对ThenBy和ThenByDescending方法的调用:
customers.
OrderBy(c => c.Country).
ThenByDescending(c => c.Balance).
Select(c => new { c.Name, c.Country, c.Balance })
26.7.1.5 多重产生器
多重产生器:
from c in customers
where c.City == "London"
from o in c.Orders
where o.OrderDate.Year == 2005
select new { c.Name, o.OrderID, o.Total }
除了最内层产生器之外,所有的都转换成对SelectMany的调用,:
customers.
Where(c => c.City == "London").
SelectMany(c =>
c.Orders.
Where(o => o.OrderDate.Year == 2005).
Select(o => new { c.Name, o.OrderID, o.Total })
)
当多重产生器与orderby子句结合时:
18 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
from c in customers, o in c.Orders
where o.OrderDate.Year == 2005
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }
额外的Select被注入进来搜集排序表达式(ordering expressions)和元组序列中的最终结果。 有必要这样
做,以至OrderBy可以操作于整个序列上。OrderBy之后,最终结果从元组中提取出来。
customers.
SelectMany(c =>
c.Orders.
Where(o => o.OrderDate.Year == 2005).
Select(o => new { k1 = o.Total, v = new { c.Name, o.OrderID, o.Total } })
).
OrderByDescending(x => x.k1).
Select(x => x.v)
26.7.1.6 info子句
info子句:
from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Group.Count() }
它是内嵌查询的一个简单而更方便的表示法:
from g in
from c in customers
group c by c.Country
select new { Country = g.Key, CustCount = g.Group.Count() }
转换如下:
customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Group.Count() })
26.7.2 查询表达式模式
查询表达式模式建立了一个方法的模式,类型可以实现它来支持查询表达式。因为查询表达式利用语
法映射转变成方法调用,类型在如何实现查询表达式模式时有很大的灵活性。例如:模式的方法可以
被实现成实例方法或扩展方法,因为这两者具有同样的调用语法;并且方法可以要求(request)
delegates或表达式树,因为lambda可以转换成这两者。
推荐使用的支持查询表达式模式的泛型类型 C<T> 的外形(shape)显示如下。泛型类型被使用来例示参数
和返回类型的适当关系,但是非泛型类型实现这个模式同样是可能的。
delegate R Func<A,R>(A arg);
class C<T>
{
public C<T> Where(Func<T,bool> predicate);
public C<S> Select<S>(Func<T,S> selector);
public C<S> SelectMany<S>(Func<T,C<S>> selector);
public O<T> OrderBy<K>(Func<T,K> keyExpr);
public O<T> OrderByDescending<K>(Func<T,K> keyExpr);
public C<G<K,T>> GroupBy<K>(Func<T,K> keyExpr);
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 19
Overview of C# 3.0
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keyExpr, Func<T,E> elemExpr);
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector);
public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}
class G<K,T>
{
public K Key { get; }
public C<T> Group { get; }
}
上面的方法使用泛型delegate类型Func<A, R>,但是同样可以以参数和返回类型中的相同关系来使用其
它delegate或表达式树类型。
注意,推荐的C<T>和O<T>之间的关系保证仅在OrderBy 或 OrderByDescending 的返回结果 上ThenBy
和ThenByDescending方法是可用的。也请注意,推荐的GroupBy结果的外形是分组的序列,每个分组
具有Key和Group属性(property)。
标准的查询操作符 (描述于单独的规范中)提供任意实现了 System.Collections.Generic.IEnumerable<T>
接口的查询操作符模式的实现。
26.7.3 正式的转换规则
查询表达式通过依次重复应用下述转换进行处理。每个转换被应用,直到不出现指定的模式为止。
注意,在产生OrderBy和ThenBy调用的转换中,如果对应的排序子句指定了一个递减方向的指示器,
OrderByDescending或ThenByDescending就会产生。
包含into子句的查询
q1 into x q2
转换成
from x in ( q1 ) q2
带有多个产生器的from子句
from g1 , g2 , … gn
转换成
from g1 from g2 … from gn
立即跟随where子句的form子句
from x in e where f
被转换成
from x in ( e ) . Where ( x => f )
多个from,一个orderby和一个select子句的查询表达式
from x1 in e1 from x2 in e2 … orderby k1 , k2 … select v
被转换成
20 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.
( from x1 in e1 from x2 in e2 …
select new { k1 = k1 , k2 = k2 … , v = v } )
. OrderBy ( x => x . k1 ) . ThenBy ( x => x . k2 ) …
. Select ( x => x . v )
多个from,一个orderby和一个group子句的查询表达式
from x1 in e1 from x2 in e2 … orderby k1 , k2 … group v by g
被转换成
( from x1 in e1 from x2 in e2 …
select new { k1 = k1 , k2 = k2 … , v = v , g = g } )
. OrderBy ( x => x . k1 ) . ThenBy ( x => x . k2 ) …
. GroupBy ( x => x . g , x => x . v )
多个from和一个select子句的查询表达式
from x in e from x1 in e1 … select v
被转换成
( e ) . SelectMany ( x => from x1 in e1 … select v )
多个from子句和一个group子句的查询表达式
from x in e from x1 in e1 … group v by g
被转换成
( e ) . SelectMany ( x => from x1 in e1 … group v by g )
一个from,没有orderby,一个select子句的查询表达式
from x in e select v
被转换成
( e ) . Select ( x => v )
除非当v是标识符x时,转换都是简单的
( e )
一个from,没有orderby,一个group子句的查询表达式
from x in e group v by g
被翻译成
( e ) . GroupBy ( x => g , x => v )
除了当v是标识符x,转换是
( e ) . GroupBy ( x => g )
Copyright 2 Microsoft Corporation 2005. All Rights Reserved. 21
Overview of C# 3.0
一个from,一个orderby,一个select子句的查询表达式
from x in e orderby k1 , k2 … select v
被转换成
( e ) . OrderBy ( x => k1 ) . ThenBy ( x => k2 ) … . Select ( x => v )
除非当v是标识符x时,翻译是简单的。
( e ) . OrderBy ( x => k1 ) . ThenBy ( x => k2 ) …
一个from,一个orderby,一个group子句的查询表达式
from x in e orderby k1 , k2 … group v by g
被转换成
( e ) . OrderBy ( x => k1 ) . ThenBy ( x => k2 ) …
. GroupBy ( x => g , x => v )
除非v是标识符x时,转换是
( e ) . OrderBy ( x => k1 ) . ThenBy ( x => k2 ) … . GroupBy ( x => g )
26.8 表达式树(Expression trees)
表达式树允许lambda表达式表示为数据结构而不是可执行代码。可转型到delegate类型D的lambda表
达式也可以转型到类型System.Query.Expression<D>。然而,到delegate类型的lambda表达式的转型 导
致 建立表达式树实例的代码产生(emit)。表达式树是lambda表达式的高效内存数据表示,并生成表达
式转换的结构,且使其显式化。
前面的例子表示一个lambda表达式即作为可执行代码,又作为表达式树。因为存在到Func<int,int>的
转型,也存在到Expression<Func<int,int>>的转型。
Func<int,int> f = x => x + 1; // Code
Expression<Func<int,int>> e = x => x + 1; // Data
紧随这些参数之后,delegate f引用返回x+1的方法,表达式树e引用描述表达式x+1的数据结构。
注意:
表达式树的结构将在单独的规范中。这个规范在PDC 2005技术预览会议上还不存在。
22 Copyright 2 Microsoft Corporation 2005. All Rights Reserved.