微软.NET平台中类型使用的基本原理

发表于:2007-12-04 13:38

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:未知    来源:网络转载

#
微软
#
.NET
分享:

  在本文中,首先从介绍简单类型开始,然后迅速进入关于引用类型和数值类型的讨论。对所有的开发人员来说,熟练掌握引用类型和数值类型的应用差别尤其重要。在编写代码的过程中,如果对这两种类型使用不当会导致程序Bug并引起性能问题。

  简单类型

  某些常用的数据类型,许多编译器通过简单的语法就可以对它们进行处理。例如,在C#语言中,你可以使用下列语法来分配一个整型变量:

  int a = new int(5);

  但是我敢肯定,你会觉得用这样的语法来声明和初始化一个整型变量很笨拙。好在许多编译器(包括C#编译器)允许你使用下面的语法来代替:

  int a = 5;

  这就使代码的可读性更强。不论使用那一种语法,产生的中间语言时一样的。

  凡编译器直接支持的数据类型称为简单数据类型。这些简单数据类型直接映射到基类库中存在的类型。例如C#中int类型直接映射到System.Int32。所以可以将下列两行代码与前面提到的两行代码是一样的:

  System.Int32 a = new System.Int32(5);

  System.Int32 a = 5;

  引用类型和数值类型

  当从受管堆(managed heap)中分配对象时,new操作符返回对象的内存地址。通常将这个地址存储在一个变量当中。这种方式就是引用类型的变量,因为变量不包含实际对象的位,而是引用对象的位。

  在处理引用类型时会有一些性能问题要考虑。首先,内存必须要从受管堆中分配,这样能强制垃圾回收。其次,引用类型总是通过指针来存取。所以每次引用堆中对象的成员时,为了实现期望的处理,必须要产生和执行收回指针的代码。这反而影响程序的大小和程序执行的速度。

  除了引用类型外,实际的对象系统中还有轻量级的数值类型。数值类型对象不能在可回收垃圾的堆中分配,并且表示对象的变量不包含对象的指针,而是变量包含对象本身。因为变量包含着对象,处理对象也就不必考虑指针回收的问题,从而改进了性能。

  更普通的类。在C#中,使用结构声明的类型是个数值类型,而使用类声明的是引用类型。其它语言可能用不同的语法来描述数数值类型和引用类型,例如C++中使用_value修饰符。

  // Reference Type (because of 'class')class RectRef { public int x, y, cx, cy; }

  // Value type (because of 'struct')struct RectVal { public int x, y, cx, cy; }

  static void SomeMethod { RectRef rr1 = new RectRef(); // Allocated in heap RectVal rv1; // Allocated on stack (new optional) rr1.x = 10; // Pointer dereference rv1.x = 10; // Changed on stack

   RectRef rr2 = rr1; // Copies pointer only RectVal rv2 = rv1; // Allocate on stack & copies members rr1.x = 20; // Changes rr1 and rr2 rv1.x = 20; // Changes rv1, not rv2}

  回顾前面讨论简单类型时提到过的代码行:

  System.Int32 a = new System.Int32(5);

  编译这个语句时,编译器发觉System.Int32是数值类型并优化产生的中间语言(IL)代码,以便使这个“对象”不从堆中分配;而将这个对象放到线程堆栈的局部变量a中。、

  可能的情况下,应该使用数值类型而不要使用引用类型,这样做可以使应用程序的性能更好。尤其是在使用以下数据类型时,你应该将变量声明为数值类型:

  * 简单数据类型。

  * 不需要从其它类型继承的数据类型。

  * 没有任何从它派生的数据类型。

  * 类型对象不会作为方法参数经常性传递,这是因为它会导致频繁的内存拷贝操作,从而损害性能。这一点在下面有关框入和框出的讨论中将作更详细的解释。

  数值类型的主要优点是他们不在受管堆中进行分配。但与引用类型比较,使用数值类型也有几个局限。以下是对数值类型和引用类型的一个比较。

  数值类型对象有两种表示法:框出的形式和框入的形式。引用类型对象总是表示为框入形式。

  数值类型从System.ValueType类型中隐含派生。这个类型提供的方法与System.ValueType定义的方法相同。但是,System.ValueType重载Equals方法,以便在两个对象实例字段匹配时返回true。此外,System.ValueType重载GetHashCode方法,以便在对象实例字段中使用有这些值参与的算法产生hash 代码值。当定义自己的数值类型时,强烈推荐你重载并提供外部的Equals 和GetHashCode方法实现。

  因为使用数值类型作为基类时不能声明新的数值类型或新的引用类型,数值类型不应有虚函数,不能被抽象,并被隐含式封装(封装类型不能被用作新类型的基类)。

  引用类型变量包含堆内存中对象的地址。在缺省情况下,引用类型变量被创建时被初始化为空(null),也就是说这个引用类型变量当前不指向有效对象。试图使用值为空的引用类型变量会导致NullReferenceException 异常。与之相对,对于数值类型变量来说,它总是包含潜在类型的值,在缺省情况下,这个数值类型所有成员被初始化零(zero)。当访问数值类型时就不可能产生NullReferenceException 异常。

  当你将一个数值类型变量的内容赋值给另一个数值类型变量时,变量值被拷贝。当你将一个引用类型变量的内容赋值给另一个引用类型变量时,只是变量的内存地址被拷贝。

  从以上的讨论中可以得出这样的结论,堆中的单个对象可以涉及两个以上的引用类型变量。这样就允许用作用在一个变量上的操作来影响被另一个变量引用的对象。另一方面,每一个数值类型变量都有其自己的对象数据拷贝,而且对其中一个数值类型变量的操作不会影响其它的数值类型变量。

  运行时必须初始化数值类型以及不能调用其缺省构造函数的情形很少见,例如下面的情况下会发生这种事情,当非受管线程第一次执行受管代码时必须分配和初始化线程本地数值类型。在这种情况下,运行时不能调用类型的构造函数,但仍然保证所有成员被初始化为零或者为空。为此,推荐你不要对数值类型定义无参数的构造函数。实际上,C#编译器(以及其它编译器)会认为出错并不再编译代码。这个问题很少见,而且也不会发生在引用类型上。对于数值类型和引用类型的参数化构造函数没有这些限制。

  因为框出的数值类型不在堆中分配,只要定义这个类型实例的方法不再是活动的,就可以很潇洒的为它们分配存储区域。也就是说框出的数值类型对象的内存被收回的时候是接收不到通知的。但是,框入的数值类型被当作垃圾收回时会有其Finalize方法调用。你绝不能用Finalize方法实现一个数值类型。象无参数构造函数一样,C# 认为这是一个错误而不再编译源代码。

21/212>
100家互联网大公司java笔试题汇总,填问卷领取~

精彩评论

  • ∮随风而去~
    2007-12-04 18:34:30

    凡编译器直接支持的数据类型称为简单数据类型.
    当你将一个数值类型变量的内容赋值给另一个数值类型变量时,变量值被拷贝。当你将一个引用类型变量的内容赋值给另一个引用类型变量时,只是变量的内存地址被拷贝。

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号