1回顶部 Prashant Sridharan Microsoft Corporation
适用于:Microsoft® Visual C#(TM)
摘要:Microsoft Corporation 正在开发 C# 语言的下一个主要版本。本文介绍了四种主要的新功能,即泛型、迭代程序、匿名方法和局部类型。
简介
C# 是创新性的新式编程语言,它巧妙地结合了最常用的行业语言和研究语言中的功能。在保持 C# 设计思想不变的同时,Microsoft 在 C# 语言中引入了几种潜在的新功能,提高了开发人员在语言构造方面的效率。
Microsoft C#
自 2001 年 2 月 C# 问世以来,很多开发人员已经开始使用 C# 编程语言来构建软件。而 Microsoft 自身也使用 C# 构建了几种正式的应用程序,包括 .NET Framework、MSN Web 属性和 Tablet PC SDK。由此可见,C# 是一种适用于构造高品质商业软件的语言。
C# 语言中的许多功能是基于以下四种不同设计目标而创建的:
统一的类型系统以及简化值类型和引用类型在 C# 语言中的用法。通过 XML 注释、特性、属性、事件和委托等功能建立基于组件的设计。借助 C# 语言的独特功能(包括安全的指针操作、溢出检查等)建立实用的开发人员控制功能。建立诸如 foreach 和 using 语句这样的实用语言构造,提高开发人员的效率。
在 C# 语言的“Visual Studio for Yukon”版本中,Microsoft 计划通过将广泛的研究语言和行业语言中的各种功能结合在一起建立一种简洁、实用的语法。这些语言功能包括泛型、迭代程序、匿名方法和局部类型。
潜在的未来功能
实际上,C# 的未来创新功能主要基于统一的类型系统、基于组件的开发、开发人员控制功能和实用的语言构造。下面总结了 Microsoft 计划在 C# 语言的下一个主要版本中提供的四种主要的新功能。这些功能的设计尚未完成,Microsoft Corporation 欢迎广大的开发人员针对这些功能发表评论。
泛型
随着项目变得越来越复杂,程序员日益需要一种方法来更好地重复使用和自定义他们现有的基于组件的软件。为了实现在其他语言中重复使用高级代码,程序员通常要使用一种名为“泛型”的功能。C# 将包括一种安全且高效的泛型,它与 C++ 中的模板和 Java 语言中提出的泛型在语法上只是稍有差别,但在实现方式上却存在很大差别。
生成最新的泛型类
利用目前的 C#,程序员可以通过在基本对象类型的实例中存储数据来创建有限形式的真正泛型。由于在 C# 中每个对象都是从基本对象类型继承的,再加上统一 .NET 类型系统的装箱和取消装箱功能,程序员可以将引用类型和值类型存储到对象类型的变量中。但是,对于引用类型、值类型与基本对象类型之间的转换,还有一些性能缺陷。
为了说明这一点,以下代码示例创建了一个简单的 Stack 类型,其中包含两个操作“Push”和“Pop”。Stack 类将其数据存储在对象类型的数组中,Push 和 Pop 方法使用基本对象类型来接受和返回数据:
public class Stack
{
private object[] items = new object[100];
public void Push(object data)
{
...
}
public object Pop()
{
...
}
}
然后,就可以将自定义类型(例如 Customer 类型)压入堆栈。但是,如果程序需要检索数据,则需要将 Pop 方法的结果(基本对象类型)显式转换成 Customer 类型。
2回顶部
Stack s = new Stack();
s.Push(new Customer());
Customer c = (Customer) s.Pop();
如果将一个值类型(例如一个整数)传递给 Push 方法,运行时会自动将其转换为引用类型(该过程称作装箱),然后将其存储在内部数据结构中。与此类似,如果程序要从堆栈中检索一个值类型(例如一个整数),则需要将从 Pop 方法获取的对象类型显式转换成值类型,该过程称作取消装箱:
Stack s = new Stack();
s.Push(3);
int i = (int) s.Pop();
值类型和引用类型之间的装箱和取消装箱操作非常繁重。
而且,在当前的实现中,无法限制堆栈中放置的数据类型。实际上,可以先创建堆栈,然后将 Customer 类型压入堆栈。然后,可使用同一堆栈并尝试将数据弹出,接着将其转换为其他类型,如下例所示:
Stack s = new Stack();
s.Push(new Customer());
Employee e = (Employee) s.Pop();
尽管上一个代码示例错误地使用了要实现的单个类型 Stack 类,应视为错误,但它实际上却是合法代码,对它进行编译时不会出现问题。但在运行时,该程序会由于无效转换操作而失败。
创建和使用泛型
使用 C# 中的泛型可以根据它们所用的类型创建专供编译器使用的高效数据结构。创建这些所谓的参数化类型后,其内部算法保持不变,但其内部数据的类型可以随着最终用户的设置而改变。
为了帮助开发人员节省学习该语言的时间,C# 中泛型的声明方法与 C++ 中的大致相同。程序员可以按通常的方法创建类和结构,并使用尖括号标记(< 和 >)指定类型参数。使用类时,必须用该类的用户提供的实际类型替换每个参数。
下例将创建一个 Stack 类,在此类声明后的尖括号中指定并声明一个名为 ItemType 的类型参数。泛型 Stack 类的实例将接受为其创建的类型并在本地存储该类型的数据,而不是在创建的类型与基本对象类型之间进行转换。类型参数 ItemType 充当代理,直到在实例化过程中指定了类型并将其用作内部项数组的类型(即 Push 方法的参数类型和 Pop 方法的返回类型):
public class Stack3回顶部
多个类型参数
泛型可以使用任意多个参数类型。上面的 Stack 示例中只使用了一种类型。假设您创建了一个存储值和键的简单 Dictionary 类。在程序中可以通过声明两个参数(放在类定义的尖括号中并用逗号分隔)来定义一个泛型版本的 Dictionary 类:
public class Dictionary4回顶部
多重约束
对于任何给定的类型参数,程序可以为其指定任意多个接口约束,但最多只能指定一个类约束。每个新约束都以另一个“参数-要求”对的形式进行声明,并且给定的泛型的每个约束都用逗号分隔。以下示例中的 Dictionary 类包含两种参数,KeyType 和 ValType。KeyType 类型参数有两个接口约束,而 ValType 类型参数有一个类约束:
public class Dictionary5回顶部
C# 泛型与其他实现之间的差异
C++ 模板与 C# 泛型存在着显著的差别。C# 泛型被编译成 IL,这使得在运行时会智能地为每个值类型创建相应的专用类型,而为引用类型只会创建一次专用类型;C++ 模板实际上是代码扩展宏,它为提供给模板的每个类型参数生成一个专用类型。因此,当 C++ 编译器遇到模板(例如整数 Stack)时,它会将模板代码扩展为 Stack 类并将整数作为该类本身的类型包含在其中。无论类型参数是值类型还是引用类型,如果不专门设计链接器来降低代码膨胀速度,C++ 编译器每次都会创建一个专用类,从而导致比使用 C# 泛型更显著的代码膨胀速度。
而且,C++ 模板不能定义约束。C++ 模板只能通过使用一个成员(可能属于也可能不属于类型参数),隐式定义约束。如果最终传递给泛型类的类型参数中存在该成员,程序将正常运行。否则,程序将失败,并可能返回隐藏的错误信息。由于 C# 泛型可以声明约束,并且具有严格的类型,因此不存在这些潜在的错误。
现在,Sun Microsystems® 已经在新版本的 Java 语言(代码名称为“Tiger”)中添加了其他的泛型。Sun 选择的实现不需要修改 Java 虚拟机。因此,Sun 面临着如何在未修改的虚拟机上实现泛型的问题。
提出的 Java 实现使用与 C++ 中的模板和 C# 中的泛型类似的语法,包括类型参数和约束。然而,由于它处理值类型与处理引用类型的方式不一样,因此未修改的 Java 虚拟机不支持值类型的泛型。因此,Java 中的泛型无法得到有效的执行。事实上,Java 编译器会在需要返回数据时,从指定的约束(如果声明了)或基本对象类型(如果未声明约束)插入自动向下的类型转换。此外,Java 编译器将在运行时生成一个专用类型,然后使用它实例化任何构造类型。最后,由于 Java 虚拟机本身不支持泛型,因此无法在运行时确定泛型实例的类型参数,而且反射的其他用途也会受到严重限制。
其他语言中的泛型支持
Microsoft 的目标是在 Visual J#(TM)、Visual C++ 和 Visual Basic 中支持使用和创建泛型。尽管不同语言实现此功能的时间有早有晚,但 Microsoft 的所有其他三种语言都将包含对泛型的支持。同时,C# 小组正努力在泛型的基础运行时中加入相应的功能,为实现多语言支持奠定基础。Microsoft 与第三方语言合作伙伴紧密协作,以确保在基于 .NET 的语言中创建和使用泛型。
迭代程序
迭代程序是基于研究语言中的类似功能(例如 CLU、Sather 和 Icon)而构造的语言。简单说来,通过迭代程序,类型可轻松地声明 foreach 语句对其元素进行迭代的方式。
为什么需要迭代程序
现在,如果类需要使用 foreach 循环结构支持迭代操作,则它们必须实现“枚举器模式”。例如,编译器将左侧的 foreach 循环结构扩展为右侧的 while 循环结构:
List list = ...;
foreach(object obj in list)
{
DoSomething(obj);
}
Enumerator e = list.GetEnumerator();
while(e.MoveNext())
{
object obj = e.Current;
DoSomething(obj);
值得注意的是,为了使 foreach 循环能够正常运行,List 数据结构(所迭代的实例)必须支持 GetEnumerator 函数。创建 List 数据结构后,必须实现 GetEnumerator 函数,以返回 ListEnumerator 对象:
public class List
{
internal object[] elements;
internal int count;
public ListEnumerator GetEnumerator()
{
return new ListEnumerator(this);
}
}
所创建的 ListEnumerator 对象不仅必须实现 Current 属性和 MoveNext 方法,而且还必须维护其内部状态,以便程序在每次执行该循环时都可以移到下一项。此内部状态机对于 List 数据结构而言比较简单,但对于需要递归循环的数据结构(例如二叉树)来说,该状态机将相当复杂。
由于实现此枚举器模式需要开发人员投入大量的精力并写大量代码,因此 C# 包含一种新的结构,使得类可以轻松地指示 foreach 循环对其内容进行迭代的方式。
6回顶部
定义迭代程序
由于迭代程序是 foreach 循环结构的逻辑对应物,因此其定义方式类似于函数:使用 foreach 关键字并在后面带有一对圆括号。在以下示例中,程序将为 List 类型声明一个迭代程序。迭代程序的返回类型由用户决定,但是由于 List 类内部存储的是对象类型,因此以下迭代程序示例的返回类型为对象:
public class List
{
internal object[] elements;
internal int count;
public object foreach()
{
}
}
值得注意的是,实现枚举器模式后,程序需要维护内部状态机以便跟踪程序在数据结构中的位置。迭代程序具有内置状态机。使用新的 yield 关键字,程序可以将值返回到调用该迭代程序的 foreach 语句。当 foreach 语句下次循环并再次调用迭代程序时,此迭代程序将在上一个 yield 语句停止的位置开始执行。在以下示例中,程序将生成三个字符串类型:
public class List
{
internal object[] elements;
internal int count;
public string foreach()
{
yield "microsoft";
yield "corporation";
yield "developer division";
}
}
在以下示例中,调用此迭代程序的 foreach 循环将执行三次,每次都会按照前三个 yield 语句指定的顺序接收字符串:
List list = new List();
foreach(string s in list)
{
Console.WriteLine(s);
}
如果要让程序实现迭代程序以遍历列表中的元素,则需要使用 foreach 循环修改此迭代程序使其遍历元素数组,并在每次迭代中产生数组中的每个项目:
public class List
{
internal object[] elements;
internal int count;
public object foreach()
{
foreach(object o in elements)
{
yield o;
}
}
}
迭代程序的工作原理
迭代程序代表所在的程序处理实现枚举器模式的日常操作。C# 编译器将您在迭代程序中编写的代码转换成使用枚举器模式的相应类和代码,而无需创建类和建立状态机。通过这种方式,迭代程序显著提高了开发人员的工作效率。
匿名方法
匿名方法是另一种实用的语言结构,它使程序员能够创建可装箱在委托中、并且可在以后执行的代码块。它们基于称作 λ 函数的语言概念,并且类似于 Lisp 和 Python 中的对应语言概念。
创建委托代码
委托是引用方法的对象。调用委托时,将调用它所引用的方法。以下示例举例说明了一个简单的窗体,其中包含列表框、文本框和按钮三个控件。初始化按钮时,程序将指示其 Click 委托引用该对象中其他位置存储的 AddClick 方法。在 AddClick 方法中,文本框的值存储在列表框中。由于 AddClick 方法被添加到按钮实例的 Click 委托中,因此每次单击该按钮时都将调用此方法。
public class MyForm
{
ListBox listBox;
TextBox textBox;
Button button;
public MyForm()
{
listBox = new ListBox(...);
textBox = new TextBox(...);
button = new Button(...);
button.Click += new EventHandler(AddClick);
}
void AddClick(object sender, EventArgs e)
{
listBox.Items.Add(textBox.Text);
}
}
7回顶部
使用匿名方法
上一个示例非常直观。其中创建了一个单独的函数,并对其进行了委托引用,每当调用此委托时,程序都会调用该函数。在该函数中,执行了一系列的可执行步骤。使用匿名方法,程序无需为该类创建整个新方法,而可以直接引用委托中包含的可执行步骤。匿名方法的声明方法是先实例化一个委托,然后在实例化语句之后加上一对表示执行范围的花括号,最后加上一个用于终止语句的分号。
在以下示例中,程序修改委托创建语句以直接修改列表框,而不是引用代表程序来修改该列表框的函数。存储代码的目的是为了修改委托创建语句之后的执行范围中的列表框。
public class MyForm
{
ListBox listBox;
TextBox textBox;
Button button;
public MyForm()
{
listBox = new ListBox(...);
textBox = new TextBox(...);
button = new Button(...);
button.Click += new EventHandler(sender, e)
{
listBox.Items.Add(textBox.Text);
};
}
}
请注意,“匿名”方法中的代码是如何访问和处理其执行范围以外声明的变量的。实际上,匿名方法可以引用由类和参数声明的变量,也可以引用所在方法声明的局部变量。
向匿名方法传递参数
有趣的是,“匿名”方法语句包含两个参数,即 sender 和 e。查看 Button 类的 Click 委托的定义,您会发现委托引用的任何函数都必须包含两个参数,第一个参数为对象类型,第二个参数为 EventArgs 类型。在第一个示例中,程序未使用“匿名”方法,而是向 AddClick 方法传递了两个参数,类型分别为对象和 EventArgs。
即使以内联方式编写此代码,委托仍必须接收两个参数。在“匿名”方法中,必须声明两个参数的名称,这样关联的代码块才能使用它们。当触发按钮上的 Click 事件时,将调用“匿名”方法并将相应的参数传递给该方法。
匿名方法的工作原理
遇到“匿名”委托时,C# 编译器会自动将其执行范围内的代码转换为唯一命名类中的唯一命名函数。然后将设置存储代码块的委托,以引用编译器生成的对象和方法。调用委托时,将通过编译器生成的方法执行“匿名”方法块。
局部类型
尽管在单个文件中维护类型的所有源代码是面向对象编程的好方法,但有时性能约束会使得类型变大。此外,在某些情况下将类型分割成子类型所耗费的开销是无法让人接受的。而且,程序员经常会创建或使用应用程序来发布源代码和修改结果代码。遗憾的是,当再次发布源代码时,所有现有的源代码修改将被覆盖。
局部类型允许您将包含大量源代码的类型分割成多个不同的源文件,以便于开发和维护。此外,局部类型可用于将计算机生成的类型部分与用户编写的类型部分分隔开,从而更易于补充或修改工具生成的代码。
在以下示例中,两个 C# 代码文件 File1.cs 和 File2.cs 中都定义了名为 Foo 的类。如果不使用局部类型,将会出现编译错误,因为这两个类存在于同一个命名空间中。使用 partial 关键字,可以指示编译器:别处可能包含此类的其他定义。
File1.cs File2.cs
public partial class Foo
{
public void MyFunction()
{
// 在此处执行操作
}
}
public partial class Foo
{
public void MyOtherFunction()
{
// 在此处执行操作
}
}
编译时,C# 编译器将收集局部类型的所有定义并将它们组合在一起。编译器生成的结果 IL 显示了组合而成的单个类,而不是将多个类分别作为单独的类进行连续显示。
8回顶部
符合标准
2001 年 12 月,欧洲计算机制造商协会 (ECMA) 将 C# 编程语言批准为一项标准 (ECMA 334)。此后不久,C# 标准便得到国际标准化组织 (ISO) 的快速跟踪处理,预计很快就会得到批准。C# 标准的创建是新编程语言发展史中的重要里程碑,它预示着未来有望在各种操作系统平台上编写多种实现。实际上,我们从其简短的历史中可以看到,许多第三方编译器供应商和研究人员已经将它当作标准来实现并创建了自己的 C# 编译器版本。
Microsoft 欢迎客户对在 C# 语言中添加上面提到的功能提供反馈意见,并打算将这些功能提交给正在进行的语言标准化进程。
可用性
下面介绍的功能将在 C# 编译器的未来版本中实现。2003 年年初,Visual Studio .NET 的“Everett”版本将包含为完全符合 ECMA 标准而略作修改的 C# 版本。此版本不包含本文介绍的功能。Microsoft 打算在 Visual Studio 的“VS for Yukon”版本中包含这些功能,但具体的发布日期还未确定。
在接下来的几个月中,Microsoft 将发布有关这些功能的详细信息,包括所有规范。欢迎广大程序员和语言设计团体就这些功能以及任何其他感兴趣的语言功能提出自己的看法和反馈。您可以将电子邮件发送到 mailto:sharp@microsoft.com,与 C# 语言设计人员取得联系。
|
正在阅读:C# 编程语言的未来功能C# 编程语言的未来功能
2009-03-12 23:34
出处:PConline
责任编辑:ycx