-- 作者:admin
-- 发布时间:11/9/2004 2:25:00 AM
-- 包装很好,里面是什么? (转载)
发信人: Jobs (温少), 信区: DotNET 标 题: 包装很好,里面是什么? (转载) 发信站: BBS 水木清华站 (Wed May 2 00:49:49 2001) 包装很好,里面是什么? 作者:Eric Gunnerson 原文URL:http://www.microsoft.com/china/msdn/voices/csharp02152001.asp C# 中的类型 C# 和公共语言运行时 (CLR) 中有两种类型:引用类型(在 C# 中用类声明)和值 类型(在 C# 中用结构声明)。引用和值类型在几个重要方面有所不同。下表概括 了这些区别: 引用(类) 值(结构) 保留变量 引用 实际值 活动值 在堆中 内联(在堆栈中或与对象内联) 默认值 Null 零 = 表示 复制引用 复制值 值类型“感觉上”象一个数据。它包括预定义数值类型以及用户定义的类型(如 Complex 数字、Point 或 Rectangle)。如上文所述,值类型的变量是实际的值, 所以在您使用变量时,通常处理的是实际的值。 int i = 123; int j = i; i = 55; 在第二次指定变量后,两个独立的变量包含相同的值。修改 i 的值不会改变 j 的 值。 引用类型用于所有不能用作值类型的对象。引用类型的变量指向堆中对象的实例。 这意味着在将一个变量指定给另一个变量时,只是指定了引用,而不是值。 Employee e = new Employee("Fred"); Employee f = e; f.Name = "Barney"; 在第二次指定变量后,e 和 f 指向同一对象。这意味着修改 f 的名称也将改变 e 的名称,因为它们引用同一实例。 这引发了一个相关话题。有些人可能一直奇怪,为什么 System.String 类中的函 数不修改字符串,而总是返回字符串的新副本。这是因为字符串的类型为引用类型 。如果对字符串调用 s.Trim() 来修改内部字符串,您将遇到与 Employee 相同的 问题(这对字符串非常糟糕)。 修改类值的成员称为“变更者”,而不具有任何变更者的类称为不可变类。不可变 类的存在可以使类的行为类似于值类,但不能写入为值类。 如果您需要使用可变字符串类,请在 System.Text 中试用 StringBuilder。 转向更简单的模型 在语言中同时使用引用和值两种类型是很重要的。值类型轻便高效,而引用类型适 用于面向对象的开发。但是,现在我们有两种类型,而我们需要的是更为简单的模 型,使用单一的、能够囊括所有可能值的类型。 这样一个通用基类能够: 调用任何值的虚函数。 写入能够存储任何值的集合类。 替代 OLE Automation Variant 类型。 为实现这一目的,公共语言运行时采用一种方法让值类型在需要时转化为引用类型 ,即通过称为包装的进程。被包装的类型是通用基类,可以被各种类型的对象引用 。 包装和解除包装 考虑下列代码: int value = 123; object o = value; // 将 int 包装到对象中 int value2 = (int) o; // 解除包装到 value2 当赋值给 o 时,作为赋值的一部分,C# 编译器将创建足够容纳堆中 int 的引用 类型包装,将值复制到该包装,然后将包装标记为实际类型(此处为 System. Int32),以便运行时了解包装的类型。 要从包装中取值,必须使用强制类型装换来指定包装的类型(对象能够保留任何类 型)。在执行过程中,运行时将检查对象变量引用的类型是否为强制类型转换中指 定的类型。如果类型正确,值将从包装中复制回值类型变量。如果类型不正确,将 导致异常。 请注意解除包装过程中不会进行其他转换;类型必须完全匹配。换句话说,如果我 们编写代码: long value2 = (long) o; // 包装的值是 int 其中 o 为已包装的 int,则将导致异常。但是,我们可以这样编写: long value2 = (long)(int) o; 则转换将正常进行。 虽然这个示例演示了包装和解除包装,但可能会造成一些误解。编写代码进行包装 的情况非常少见,一般是在将值类型的变量传递给类型对象参数时使用。 下面是一个小测验。希望大家用心做。 测验:您对包装了解多少? 下面的代码段给出了不同方案。请阅读这些代码,判断哪一段代码涉及包装,而哪 一段与包装无关。有些方案(如 B)需要检查多个位置。 // 方案 1 int total = 35; DateTime date = DateTime.Now; string s = String.Format("Your total was {0} on {1}", total, date); // 方案 B Hashtable t = new Hashtable(); t.Add(0, "zero"); t.Add(1, "one"); // 方案 c DateTime d = DateTime.Now; String s = d.ToString(); // 方案 IV int[] a = new int[2]; a[0] = 33; // 方案 101 ArrayList a = new ArrayList(); a.Add(33); // 方案 vi MyStruct s = new MyStruct(15); IProcess ip = (IProcess) s; ip.Process(); 答案 请检查答案并记录分数。 方案 1 String.Format() 将字符串作为第一个参数,对象作为第二个和第三个参数。 int 和 DateTime 都是值类型,所以它们都将被包装以作为第二个和第三个参数。 String.Format() 使用这些参数,然后对每个参数调用 object.ToString() 将其 转换为字符串表示。如果您知道 int 将被包装,得一分;如果知道 DateTime 被 包装,得一分。 方案 B Hashtable.Add() 有两个参数,一个是关键字,另一个是值。它们都是类型对象。 传递给关键字参数的值为整数,所以它必须被包装,才能作为对象传递。传递给值 参数的值为字符串(引用类型),所以字符串不需要包装。每一点判断正确各得一 分。 方案 c 这段代码很迷惑人。包装的目的之一是实现对值类型参数的虚函数调用。 ToString() 是对象的虚函数,所以,看起来在调用 ToString() 时,d 将被包装 。但是在转换对象时没有使用 d,所以不需要进行包装。编译器知道类型为 DateTime 的变量只能为该类型(因为没有导出的值类型,所以该变量不能为导出 类型),所以它可以直接调用 DateTime.ToString(),并设置“这个”引用,使其 指向堆栈中的 d。如果回答正确,得一分。 方案 IV CLR 中的数组直接保存它们的值。例如,一个有五个元素的 int 数组有足够的空 间保存 5 个 int,而不是 5 个对象。如果认为此代码与包装无关,得一分。 方案 101 ArrayList.Add() 将对象作为参数,所以整数 33 将被包装。如果回答正确,得一 分。 方案 vi 接口为引用类型,所以在将值类型强制转换到接口实现时,必须包装值类型。如果 了解这一点,得一分。 最后得分 将所有分数相加,并使用下表来检查对包装的了解程度: 分数 说明 8 对包装很了解 6-7 了解,但有时明白,有时糊涂 3-5 尚可,请再接再厉 1-2 需要继续学习 0 尚未入门 摘要 最后总结一下包装。包装使编写和使用具有通用对象参数的函数变得简单而直接。 和许多美好的事情一样,包装也有不好的一面。下个月,我们将讨论这些不好的方 面是什么,如何减少负面影响,以及对 C# 的进一步说明(这会让我们的 C# 生活 更加美好)。 C# Web 站点荟萃 http://www.csharphelp.com http://dotnetwire.com -- ※ 来源:·BBS 水木清华站 smth.org·[FROM: 210.39.3.110] 返回上一页 回到目录 回到页首 下一篇
|