英语原文共 33 页,剩余内容已隐藏,支付完成后下载完整资料
第8章:常量和字段
在本章中,我将向您展示如何将数据成员添加到类型中。具体来说,我们来看看
常数和字段
常量
常量是具有永不改变的值的符号。定义常量符号时,其值必须在编译时可确定。然后,编译器将常量的值保存在模块的元数据中。这意味着您只能为编译器认为是基本类型的类型定义常量。要记住的另一点是常量始终被视为类型的一部分,而不是实例的一部分;这很好,因为常数的值永远不会改变。
注意
在C#中,以下类型是基元,可用于定义常量:Boolean,Char,Byte,SByte,Decimal,Int16,UInt16,Int32,UInt32,Int64,UInt64,Single,Double和String。
使用常量符号时,编译器会在定义常量的模块的元数据中查找符号,提取常量的值,并将值嵌入到发出的IL代码中。因为常量的值直接嵌入到代码中,所以常量在运行时不需要为它们分配任何内存。此外,您无法获取常量的地址,也无法通过引用传递常量。这些约束也意味着常量没有良好的跨模块版本控制故事,因此只有当您知道符号的值永远不会改变时才应使用它们。 (将Maxint32定义为327 67就是一个很好的例子。)让我准确地说明我的意思。首先使用以下代码并将其编译为DLL程序集:using System;
公共类组件{
//注意:C#不允许你为常量指定静态//因为常量总是隐式静态的。 public const Int32 MaxEntriesInList = 50;
}
然后使用以下代码构建一个应用程序:using System;
class App {static void Main(){
Console.WriteLine(“列表中支持的最大条目数:”
Component.MaxEntriesInList);
}
}
您会注意到此应用程序代码引用了MaxEntriesInList常量。当编译器构建应用程序代码时,它会看到MaxEntriesInList是一个值为50的常量文字,并在应用程序的IL代码中嵌入Int32值50。实际上,在构建应用程序代码之后,DLL程序集甚至不会在运行时加载
时间,可以从磁盘中删除。
此示例应该使版本控制问题显而易见。如果开发人员将MaxEntriesInList常量更改为1000并重建DLL程序集,则应用程序代码不受影响。要使应用程序获取新值,还必须重新编译。如果需要在运行时(而不是编译时)由另一个模块拾取的一个模块中有值,则不能使用常量。相反,您可以使用只读字段,我将在下面讨论。
字段
字段是一个数据成员,它包含值类型的实例或对引用类型的引用。 CLR支持类型(静态)和实例(非静态)字段。对于类型字段,当类型加载到AppDomain中时,将分配用于保存字段的动态内存(请参阅第20章),这通常在第一次引用该类型的任何方法都是JIT编译时发生。对于实例字段,在构造类型的实例时分配用于保持字段的动态存储器。CLR支持只读字段和读/写字段。大多数字段都是读/写字段,这意味着字段的值可能会随着代码的执行而多次更改。但是,只读字段只能在构造函数方法中写入(在首次创建对象时只调用一次)。编译器和验证确保除了构造函数之外的任何方法都不会写入只读字段。
让我们从“常量”部分中获取示例,并使用静态只读字段修复版本控制问题。这是DLL程序集代码的新版本:
使用系统;
公共类组件{
//需要静态将字段与类型相关联。 public static readonly Int32 MaxEntriesInList = 50;
}
这是你必须做出的唯一改变;应用程序代码根本不需要更改,但您必须重新构建它以查看新行为。现在,当应用程序的主要
方法运行时,CLR将加载DLL程序集(因此现在需要在运行时使用此程序集)并从分配给它的动态内存中获取MaxEntriesInList字段的值。当然,价值将是50。
假设DLL程序集的开发人员将50更改为1000并重建程序集。当重新执行应用程序代码时,它将自动获取新值:1000。在这种情况下,应用程序代码不必重建,只是起作用(尽管其性能受到不利影响)。需要注意的是:此方案假定新版本的DLL程序集没有强名称,或者应用程序的版本控制策略是CLR加载此新版本。
上面的示例显示了如何定义与类型本身关联的只读静态字段。您还可以定义读/写静态字段以及只读和读/写实例字段,如下所示:public class SomeType {
//这是一个静态只读字段;在运行时初始化此类时,将计算其值并将其存储在内存中。 public static readonly Random random = new Random();
//这是一个静态读/写字段。 static Int32 numberOfWrites = 0;
//这是一个实例只读字段。 public String readonly pathName =“Untitled”;
//这是一个实例读/写字段。 public FileStream fs;
public SomeType(String pathName){
//此行更改只读字段。
//这没关系,因为代码在构造函数中。 this.pathName = pathName;
}
public String DoSomething(){
//该行读写静态读/写字段。 numberOfWrites = numberOfWrites 1;
//此行读取只读实例字段。 return pathName;
}
}
在此代码中,许多字段都是内联初始化的。 C#允许您使用这种方便的内联初始化语法来初始化类型的常量,读/写和只读字段。正如您将在第9章中看到的那样.C#将内联字段初始化为用于在构造函数中初始化字段的简写语法。
第9章:方法
实例构造函数
构造函数是允许将类型的实例初始化为良好状态的方法。要使代码可以验证,公共语言运行库(CLR)要求每个类(引用类型)至少在其中定义一个构造函数。 (如果要防止类外的代码创建类的任何实例,则此构造函数可以是私有的。)创建引用类型的实例时,为实例分配内存,对象的开销字段(方法表指针和SyncBlocklndex) )被初始化,并调用类型的实例构造函数来设置对象的初始状态。
构造引用类型对象时,在调用类型的实例构造函数之前,始终将为该对象分配的内存清零。构造函数未显式覆盖的任何字段都保证值为0或null。
默认情况下,许多编译器(包括C#)在未明确定义自己的构造函数时为引用类型定义公共的无参数构造函数(通常称为默认构造函数)。例如,以下类型具有公共的无参数构造函数,允许任何可以访问该类型的代码构造该类型的实例。 class SomeType {
// C#自动定义一个默认的public,无参数构造函数。
}
前面的类型与以下类型定义相同:class SomeType {public SomeType(){}
}
类型可以定义多个实例构造函数。每个构造函数必须具有不同的签名,并且每个签名者可以具有不同的可访问性对于可验证的代码,类的实例构造函数必须在访问基类的任何继承字段之前调用其基类的构造函数。许多编译器(包括C#)会自动生成对基类构造函数的调用,因此您通常不必担心或考虑这一点。最终,System.Object的public,无参数构造函数被调用。这个构造函数什么也不做,只是返回。
在少数情况下,可以在不调用实例构造函数的情况下创建类型的实例。特别是,调用Object MemberwiseClone方法分配内存,初始化对象的开销字段,然后将源对象的字节复制到新对象。此外,在反序列化对象时通常不会调用构造函数。
C#提供了一种简单的语法,允许在构造引用类型对象时初始化字段:
class SomeType {
Int32 x = 5;
}
构造SomeType对象时,其x字段将初始化为5.这是如何发生的?好吧,如果你检查SomeType的构造函数方法(也称为.ctor)的中间语言(IL) Figure 9-1.
图9-1:SomeType的构造函数方法的IL代码
在图9-1中,您可以看到SomeType的构造函数包含将5存储到x中的代码,然后调用基类的构造函数。换句话说,C#编译器允许
方便的语法,允许您内联初始化实例字段并将其转换为构造函数方法中的代码以执行初始化。这意味着您应该了解代码爆炸。想象一下下面的类:class SomeType {
Int32 x = 5;
String s =“你好”;
双d = 3.14159;
字节b;
//这是一些构造函数。 public SomeType(){...} public SomeType(Int32 x){...} public SomeType(String s){...; d = 10; }
}
当编译器为三个构造函数方法生成代码时,每个方法的开头都包含初始化x,s和d的代码。在这个初始化代码之后,编译器
向方法追加构造函数方法中出现的代码。例如,
为构造函数生成的带有String参数的代码包括初始化x,s和d的代码,然后用值10覆盖d。注意,即使没有代码存在显式初始化,也保证b被初始化为0。
因为前面的类中有三个构造函数,所以编译器生成代码以按照构造函数初始化x,s和d三次。如果你有几个初始化的实例字段和许多重载的构造函数方法,你应该考虑在没有初始化的情况下定义字段,创建一个执行公共初始化的构造函数,并让每个构造函数显式调用公共初始化构造函数。这种方法将减少生成代码的大小。 class SomeType {
//这里没有代码来显式初始化字段
Int32 x;
字符串s;
双d;
字节b;
//必须由所有其他构造函数调用此构造函数。
//此构造函数包含初始化字段的代码。 public SomeType(){
x = 5;
s =“你好!”; d = 3.14159;
}
//此构造函数首先调用默认构造函数。 public SomeType(Int32 x):this(){this.x = x;
}
//此构造函数首先调用默认构造函数。 public SomeType(String s):this(){this.s = s;
}
}
值类型构造函数与引用类型构造函数的工作方式完全不同。首先,CLR不要求值类型在其中定义任何构造函数方法。实际上,许多编译器(包括C#)都不提供默认无参数值类型
构造函数。区别的原因是可以隐式创建值类型。检查以下代码:struct Point {public Int32 x,y;
}
class Rectangle {public Point topLeft,bottomRight;
要构造Rectangle,必须使用new运算符并且必须指定构造函数。在这种情况下,将调用由C#编译器自动生成的构造函数。为Rectangle分配内存时,内存包括Point值类型的两个实例。出于性能原因,CLR不会尝试为引用类型中包含的每个值类型调用构造函数。但正如我前面提到的,值类型的字段被初始化为0 / null。
CLR允许您在值类型上定义构造函数。这些构造函数执行的唯一方法是编写代码以显式调用其中一个,如此处所示的Rectangle的构造函数:struct Point {public Int32 x,y;
public Point(Int32 x,Int32 y){this.x = x; this.y = y;
}
}
class Rectangle {public Point topLeft,bottomRight;
public Rectangle(){
//在C#中,值类型上的new只允许构造函数//初始化已分配的值类型的内存。
topLeft = new Point(1,2);
bottomRight = new Point(100,200);
}
}
值类型的实例构造函数仅在显式调用时执行。因此,如果Rectangle的构造函数没有使用new运算符初始化其topLeft和bottomRight字段来调用Point的构造函数,则两个Point字段中的x和y字段将为0。
在前面定义的Point值类型中,未定义默认的无参数构造函数。但是,让我们重写该代码如下:
struct Point {public Int32 x,y;
public Point(){
x = y = 5;
}
}
class Rectangle {public Point topLeft,bottomRight;
public Rectangle(){
}
}
现在,当构造一个新的Rectangle时,您认为两个Point字段(topLeft和bottomRight)中的x和y字段将被初始化为:0或5? (提示:这是一个技巧问题。) 全文共28708字,剩余内容已隐藏,支付完成后下载完整资料
资料编号:[1841]
以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。