.NET框架程序设计(修订版)-- 笔记(二)

第Ⅱ部分 类型与通用语言运行时

第四章     类型基础

4.1所有类型的基类型:System.Object

       所有类型都继承至System.Object,因此会具有部分基本公有方法及受保护的方法。

       基本方法:EqualsGetHashCodeToStringGetType  P.1164.1

       受保护方法:MemberwiseCloneFinalize P.1164.2

       CLR要求所有的对象都要用new操作符来创建(该操作符将产生newobj IL指令)

       new操作符所执行的工作:

    从托管堆(managed heap)中分配指定类型所需数量的字节来作为存储其对象的内存空间;

    初始化对象的附加成员(overhead members)

    传入new操作符中指定的参数,调用类型的实例构造器;

    new完成上述操作会将返回一个指向新创建对象的引用;

    CLR使用垃圾收集环境自动检测不在被使用或访问的对象,并释放内存;

4.2类型转换

       CLR最重要的一个特性就是类型安全。

       CLR会检查转型操作以确保总是将对象转型它的实际类型、或者它的任何基类型,否则将抛出InvalidCastException异常。

       4.2.1使用isas操作符转型

              C#提供了一种利用is操作符进行转型的方式,返回判断结果:true或者false,并永远不会抛出异常。

              as操作符,省去is操作符的判断,如果兼容,返回一个指向同一个对象的非空指针,否则,返回null

4.3命名空间与程序集

       命名空间允许我们对相关类型进行逻辑上的组织,这使得我们可以很方便地定位一个类型。

       C#中的using指示符会指示编译器试着在类型名上添加不同的前缀,直到找到一个匹配为止。

       实际上,CLR对命名空间一无所知,它必须要完整的名称以及哪个程序集包含着类型的定义,它才能加载正确的程序集,找到需要的类型,进行相关操作。

       消除同名二义性:

    使用完整名称,Wintellect.Widget w = new Wintellect.Widget();

    对命名空间使用别名,using WintellectWidget = Wintellect.Widget;

定义命名空间

       namespace 命名空间名{……类、命名空间(可嵌套)}

       命名空间和程序集(实现类型的文件)并非必然相关。

第五章     基元类型、引用类型与值类型

5.1基元类型

       编译器直接支持的数据类型称为基元类型(primitive type),如int a = 0;

       基元类型和.NET框架类库(FCL)中的类型有直接的映射关系。

       如:int a=0;System.Int32 a = 0;int a = new int();System.Int32 a = new System.Int32();是一样的。

       FCL类型及其在C#中对应的基元类型 P.128 5.1

       作者推荐,使用FCL类型名而不是用基元类型名:

    很多开发人员都困惑于不知在代码中使用string还是String,其实string映射了String

    C#中,long映射为System.Int64,但是其他的编程语言中,long可能映射为Int32或者根本不存在。

    FCL中有很多类型的方法都将一些类型名作为方法名称的一部分。

编译器能够在基元类型之间进行隐式或者显式的转型:

       隐式:Int32 i=5;Int64 l=i;Single s = i;显式:Byte b = (Byte)i;Int16 v = (Int16)s;

如果是安全的转换,将直接隐式转换,如果不安全(可能丢失精度或数量级),将要求显示转换。

除了转型,基元类型还能够以文本常量的形式出现,并能在编译时计算该表达式,提高代码性能。

123.ToString()+”aaa”  -- “123aaa”Int32 x = 100+20+3  -- 123;

编译器会自动解析出现在代码中的操作符。

5.1.1CheckedUnchecked基元类型操作

       针对溢出的检查,如:

              Byte b = (Byte) checked(b+200);   -- 使用checked的情况下将抛出OverflowException

              语句块方式:checked{ Byte b = (Byte)( b+200) },将对语句块中的所有语句进行检查,不包括语句块中的方法

       使用checkedunchecked的推荐原则:

    希望在出现溢出是抛出异常,显示使用checked

    希望即使出现溢出,也不要抛出异常,显示使用unchecked

    对于没有使用这两个的代码来说,在开发阶段将会抛出异常,发布后就不会再检查

对于CLR来说Decimal不属于基元类型,checked对它没有作用,同时,它的运行效率也比较低

       5.2引用类型与值类型

              引用类型(reference type)总是从托管堆上分配,C#new操作符返回的就是对象位于托管堆中的内存地址——该内存地址指向对象占用的数据位。

              引用类型对于性能的考虑:①内存必须从托管堆中分配;②每个在托管堆中分配的对象都有一些与之关联的额外附加成员必须被初始化;③从托管堆中分配对象可能会导致执行垃圾收集。

              值类型(value type)实例通常分配在线程的堆栈上(虽然它们也可以被嵌入到一个引用类型对象中)。标识值类型实例的变量不包含指向实例的指针——变量本身即包含了实例的所有字段,所以操作实例时无序再解析指针引用,不受垃圾收集器的控制。

              任何被称为“类”的类型都是引用类型。“结构”或者“枚举”称为值类型,如:System.Boolean等。值类型继承自System.ValueType类型。System.ValueType继承自System.Object。值类型可以实现一个或多个接口,但是不能作为父类被继承。无法同步多个线程来对它进行访问。即使该类型实现的接口类型也需要对其装箱,因为接口总是引用类型。

              值类型的实例化:

使用new操作符,会对结构中的字段值做初始化,否则将不会初始化结构中的字段。

              考虑将类型声明为值类型:

    该类型行为类似于基元类型;

    该类型不需要继承自任何其他类型;

    该类型不会被任何其他类型继承;

    该类型的实例不会频繁地用于方法的参数传递;

    该类型的实例不会作为方法的结果频繁的返回;

    该类型的实例不会被频繁地用于诸如ArrayListHashtable之类的集合中;

值类型和引用类型的差别:

    值类型对象有两种表示:未装箱和装箱。引用类型总是装箱形式;

    值类型都继承自System.ValueType;

    值类型中所有方法都隐含为密封(sealed)方法不能重写。

    引用类型变量包含着对象在托管堆中的内存地址。值类型变量总是包含一个符合它类型的值,并且所有字段都被初始化为0值。

    值类型变量赋值给另一个值类型变量是一个“字段对字段”的拷贝。二引用类型是拷贝的内存地址。

    两个或多个引用类型变量可以指向托管堆中的同一个对象,这样对一个变量的操作将影响到其他变量引用的对象。值类型则都有一份自己的“对象”数据拷贝,不会影响到其他的值类型。

    值类型不处于活动状态则内存中的存储空间将立即释放,垃圾回收对它没有用处。

5.3值类型的装箱与拆箱

       装箱(boxing)将一个值类型转换为一个引用类型。通常由以下几步组成:

    从托管堆中为新生成的引用类型对象分配内存;

    将值类型实例的字段拷贝到托管堆上新分配对象的内存中;

    返回托管堆中新分配对象的地址;

拆箱(unboxing)将引用类型转为值类型,首先获取已装箱的对象中,数据值类型的那部分字段的地址,然后将这些字段的值从托管堆拷贝到位于线程堆栈上的值类型实例中。拆箱操作的代价要小的多。

/拆箱操作会从速度和内存两方面损伤应用程序的性能,应尽量避免。

对一个引用类型的拆箱操作通常由以下几步组成:

    如果该引用为null,将会抛出一个NullReferenceException异常;

    如果该引用指向的对象不是一个期望的值类型的已装箱对象,将会抛出InvalidCastException异常;

    一个指向包含在已装箱对象中值类型部分的指针被返回。

对一个对象执行拆箱操作时,转型的结果必须是它原来未装箱时的类。

IL查看器中:box [mscorlib] 代表装箱;unbox [mscorlib]代表拆箱。

对于Int32 a = 1;Console.Write(a+”aa”);可以使用a.ToString()方法减少装箱次数。

而直接Console.Write(a);的话,则不会进行装箱,因为Console.Write(Int32 value);有此参数,对于很多类似的重载方法,都是为了减少装箱操作。

第六章     通用对象操作

6.1对象的等值性与唯一性

       Equals进行比较两个引用是否指向同一个对象。实现自己的需求重写Equals时,必须:

    必须是自反的x.Equals(x) == true;

    必须是对称的x.Equals(y)y.Equals(x)返回同样的值;

    必须是可传递的x.Equals(y)y.Equals(z)都返回true,那么x.Equals(z)也要是true

    必须是前后一致的,如果两个对象的值没有发生改变,多次调用Equals返回的应该是一致的;

6.1.1为基类没有重写Object.Equals方法的引用类型实现Equals

       重写的Equals方法应该调用Object.Equals的方法进行比较,应判断传入的参数是否为null。值类型直接使用该值类型的Equals方法进行比较。

6.1.2为基类重写了Object.Equals方法的引用类型实现Equals

       与上面唯一的差别是需要使用base.Equals与基类进行比较。

6.1.3为值类型实现Equals方法

       建议实现一个强类型版本的Equals,让其接受定义类型为参数。可以提供类型安全避免装箱操作。同时重写基类Equalspublic Boolean Equals(MyValType obj) – 强类型,通过Object.Equals对比引用和自身的值对比。

6.1.4Equals方法与==/!=操作符的实现总结

       基元类型,编译器提供了默认的Equals和操作符实现。自己定义的引用类型,应该重写Equals,并可以通过操作符重载让==/!=调用重写后的Equals方法。值类型,定义个一个类型安全的强类型Equals

6.1.5对象唯一性识别

       Object.ReferenceEquals方法判断两个类型实例是否相等,其内部调用的就是==操作符。

       6.2对象的散列码

              System.Object.GetHashCode提供了一个Int32类型的散列码,要重写Equals后必须重写GetHashCode方法,否则出现警告信息。因为两个相等的对象要拥有相同的散列码值。

              重写计算类型实例的散列码算法时,应该遵循:

    算法应该使所得的数值有一个良好的随机分布,这样散列表可以获得最佳的性能。

    算法还可以调用基类的GetHashCode方法,并将其返回值包含 在我们自己的算法中。

    算法应该使用至少一个实例字段。

    理想的情况下,我们在算法中使用的字段都应该是恒定不变的。(初始化到生存期结束)

    算法应该尽可能快。

    有着相同值的对象应该返回相同的散列码。

6.3对象克隆

       如果希望自己的实例被克隆,应该事先ICloneable接口。

              浅拷贝(shallow copy)当对象的字段值被拷贝时,字段引用的对象不会被拷贝。

              深拷贝(deep copy)对象实例中字段引用的对象也进行拷贝的一种方式。(等于新创建一个对象)

       实现浅拷贝,重写Clone方法时,调用System.Object.MemberwiseClone即可。

       实现深拷贝,重写Clone方法时,返回new的一个新的对象,将字段值也全部复制下来。

posted @ 2009-12-19 17:19  壊小子  阅读(278)  评论(0编辑  收藏  举报