VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > C#教程 >
  • c#编程语言的未来功能

制作者:剑锋冷月 单位:无忧统计网,www.51stat.net
 

  简介

  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 类型。

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 Stack<ItemType>
{
  private ItemType[] items;
  public void Push(ItemType data)
  {
   ...
  }
  public ItemType Pop()
  {
   ...
  }
}

 

  当程序按照以下示例使用 Stack 类时,您可以指定泛型类使用的实际类型。本例使用实例化语句中的尖括号标记将原始的整数类型指定为参数,指示 Stack 类使用此类型:

Stack<int> stack = new Stack<int>();
stack.Push(3);
int x = stack.Pop();

  执行此操作时,程序将创建 Stack 类的新实例,其中的每个 ItemType 都被提供的整数参数替换。实际上,当程序用整数参数创建 Stack 类的新实例时,在 Stack 类内部本地存储的项目数组将为整数,而不是对象。程序还消除了与将整数压入堆栈相关联的装箱问题。此外,当程序从堆栈弹出项目时,您无需将其显式转换为相应的类型,因为 Stack 类的当前特定实例会将整数本地存储在其数据结构中。

  如果希望程序在 Stack 类中存储其他类型的项目,则必须创建一个 Stack 类的新实例并将新类型指定为参数。假设有一个简单的 Customer 类型,希望程序使用 Stack 对象存储该类型。要实现此操作,只需实例化 Stack 类并将 Customer 对象作为其类型参数,即可轻松重复使用程序代码:

Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
Customer c = stack.Pop();

  当然,如果程序创建了一个将 Customer 类型作为参数的 Stack 类,则只能在该堆栈中存储 Customer 类型。实际上,C# 中的泛型具有严格的类型,这意味着您不能在该堆栈中存储整数,如以下示例所示:

Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
stack.Push(3) // 编译时错误
Customer c = stack.Pop();   // 不需要类型转换。
泛型的优点

 

  使用泛型,程序员只需编写、测试和部署一次代码,即可对各种不同的数据类型重复使用该代码。第一个 Stack 示例具备此功能,第二个 Stack 示例允许程序重复使用对其应用程序性能影响不大的代码。对于值类型,第一个 Stack 示例具有较大的性能问题,而第二个 Stack 示例完全消除了这种问题,因为它去除了装箱和向下的类型转换。

  而且,编译时还会对泛型进行检查。当程序使用提供的类型参数实例化泛型类时,此类型参数只能是程序在类定义中指定的类型。例如,如果程序创建了一个 Customer 对象类型的 Stack,就无法将整数压入堆栈。通过强制执行这种操作,可以生成更可靠的代码。

  此外,与其他严格的类型实现相比,泛型的 C# 实现降低了代码的膨胀速度。使用泛型创建具有类型的集合,可以在保持操作性能优势的同时避免创建每个类的特定变体。例如,程序可以创建一个参数化的 Stack 类,而无需创建用于存储整数的 IntegerStack、用于存储字符串的 StringStack 以及用于存储 Customer 类型的 CustomerStack。

  这样可以增加代码的可读性。只需创建一个 Stack 类,程序就可以将与某个堆栈相关联的所有操作封装在一个使用方便的类中。然后,在创建 Customer 类型的 Stack 时,尽管其中存储了 Customer 类型,但显而易见,程序使用的仍然是堆栈数据结构。

  多个类型参数

  泛型可以使用任意多个参数类型。上面的 Stack 示例中只使用了一种类型。假设您创建了一个存储值和键的简单 Dictionary 类。在程序中可以通过声明两个参数(放在类定义的尖括号中并用逗号分隔)来定义一个泛型版本的 Dictionary 类:

public class Dictionary<KeyType, ValType>
{
  public void Add(KeyType key, ValType val)
  {
   ...
  }
  public ValType this[KeyType key]
  {
   ...
  }
}

 

  使用该 Dictionary 类时,需要在实例化语句的尖括号中提供多个以逗号分隔的参数,并为 Add 函数和索引生成器提供正确类型的参数:

Dictionary<int, Customer> dict = new Dictionary<int, Customer>();
dict.Add(3, new Customer());
Customer c = dict.Get[3];

  约束

  通常情况下,程序并不仅仅局限于根据给定的类型参数存储数据,而是经常需要使用类型参数的成员来执行程序泛型中的语句。

  为什么需要约束

  假设在 Dictionary 类的 Add 方法中,您需要使用所提供的键的 CompareTo 方法比较项目,例如:

public class Dictionary<KeyType, ValType>
{
  public void Add(KeyType key, ValType val)
  {
   ...
   switch(key.CompareTo(x))
   {
   }
   ...
  }
}

  遗憾的是,正如预期那样,类型参数 KeyType 在编译时是泛型。如果这样编写,编译器假设对类型参数为 KeyType 的 key 实例只能执行适用于基本对象类型(例如 ToString)的操作。结果,由于 CompareTo 方法未定义,编译器将显示编译错误。然而,程序可以将 key 变量转换为包含 CompareTo 方法的对象,例如 IComparable 接口。在以下示例中,程序将 KeyType 参数类型的实例 key 显式转换为程序可以编译的 IComparable 接口:

public class Dictionary<KeyType, ValType>
{
  public void Add(KeyType key, ValType val)
  {
   ...
   switch(((IComparable) key).CompareTo(x))
   {
   }
   ...
  }
}

 

  然而,如果立即实例化 Dictionary 类而且提供的类型参数没有实现 IComparable 接口,则程序将遇到运行时错误,尤其是 InvalidCastException 异常。

  声明约束

  在 C# 中,程序可以为泛型类中声明的每个类型参数提供可选约束列表。约束表示要将一个类型构造成泛型所必须满足的要求。可以使用 where 关键字声明约束,该关键字后跟“参数-要求”对,其中“参数”必须是泛型中定义的某个参数,“要求”必须是类或接口。

  为了满足在 Dictionary 类中使用 CompareTo 方法的需要,程序可以对 KeyType 类型参数添加约束,要求传递给 Dictionary 类作为第一个参数的任何类型都必须实现 IComparable 接口,例如:

public class Dictionary<KeyType, ValType> where KeyType : IComparable
{
  public void Add(KeyType key, ValType val)
  {
   ...
   switch(key.CompareTo(x))
   {
   }
   ...
  }
}

  这样,编译代码时就会检查代码,以确保程序每次使用 Dictionary 类时,作为第一个参数传递的类型都实现了 IComparable 接口。此外,程序在调用 CompareTo 方法之前,再也无需将变量显式转换为 IComparable 接口了。

  多重约束

  对于任何给定的类型参数,程序可以为其指定任意多个接口约束,但最多只能指定一个类约束。每个新约束都以另一个“参数-要求”对的形式进行声明,并且给定的泛型的每个约束都用逗号分隔。以下示例中的 Dictionary 类包含两种参数,KeyType 和 ValType。KeyType 类型参数有两个接口约束,而 ValType 类型参数有一个类约束:

 

public class Dictionary<KeyType, ValType> where
KeyType : IComparable,
KeyType : IEnumerable,
ValType : Customer
{
  public void Add(KeyType key, ValType val)
  {
   ...
   switch(key.CompareTo(x))
   {
   }
   ...
  }
}

  运行时的泛型

  泛型类的编译方法与常规类的编译方法几乎没有差别。事实上,编译结果只不过是元数据和中间语言 (IL)。当然,为了接受代码中用户提供的类型,应对 IL 进行参数化。根据提供的类型参数是值类型还是引用类型,泛型的 IL 的用法会有所不同。

  当将值类型作为参数首次构造泛型时,运行时将使用提供的参数替换 IL 中的相应位置来创建一个专用的泛型。针对每个用作参数的唯一值类型,将一次性创建专用的泛型。

  例如,假设程序代码声明了一个由整数构造的 Stack:

Stack<int> stack;

  此时,运行时将生成一个专用的 Stack 类,并用整数替换此类的相应参数。现在,无论程序代码何时使用整数 Stack,运行时都将重复使用生成的专用 Stack 类。以下示例将创建整数 Stack 的两个实例,每个实例均使用由此整数 Stack 的运行时所生成的代码来创建:

Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();

  但是,如果在程序代码中的其他位置又创建了一个 Stack 类,并使用不同的值类型(例如长整型或用户定义的结构)作为其参数,则运行时将生成其他形式的泛型,而这时会替换 IL 相应位置中的长整型参数。为使用值类型构造的泛型创建专用类的优点是可以获得更好的性能。毕竟每个专用的泛型类都是在“本地”包含值类型,因此不必再进行转换。

 

  泛型与引用类型的工作方式稍有不同。首次使用任何引用类型构造泛型时,运行时用对象引用替换 IL 中的参数来创建专用的泛型。之后,每当使用引用类型作为参数实例化构造的类型时,无论构造的是何种类型,运行时都会重复使用先前创建的专用泛型。

  例如,假设有两个引用类型,Customer 类和 Order 类,并进一步假设您创建了 Customer 类型的 Stack:

Stack<Customer> customers;

  此时,运行时将生成专用 Stack 类,该类并不存储数据,而是存储随后填充的对象引用。假设下一行代码创建了一个其他引用类型的 Stack,称为 Order:

Stack<Order> orders = new Stack<Order>();

  与值类型不同,没有为 Order 类型创建另一个专用的 Stack 类,而是创建了专用 Stack 类的实例并设置 orders 变量来引用它。对于替换类型参数的每个对象引用,按照 Order 类型的大小分配内存空间,并将指针设置为引用该内存位置。假设您随后遇到了一行用于创建 Customer 类型的 Stack 的代码:

customers = new Stack<Customer>();

  同上一个使用 Order 类型创建的 Stack 类一样,创建了专用 Stack 类的另一个实例,并将其中包含的指针设置为引用 Customer 类型大小的内存区域。由于不同的程序在引用类型的数量上存在着很大差异,因此泛型的 C# 实现通过将引用类型的数量减少到编译器为引用类型的泛型类创建的专用类数量,大大降低了代码的膨胀速度。

  此外,当使用类型参数(无论是值类型还是引用类型)实例化泛型 C# 类时,可以在运行时使用反射和实际类型进行查询,并且可以确定其类型参数。

  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 虚拟机本身不支持泛型,因此无法在运行时确定泛型实例的类型参数,而且反射的其他用途也会受到严重限制。

 

  生成最新的泛型类

  利用目前的 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 类型。

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();

  值类型和引用类型之间的装箱和取消装箱操作非常繁重。



相关教程