《CLR via C#》第五章 读书笔记

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

对第五章内容进行简单的整理、摘要。主要是记录一些对本人来说比较陌生的东西。

第一节  编程语言的基元类型

1、编译器直接支持的数据类型称为基元类型(primitive type)。

2、C#的string关键字直接映射到System.String(一个FCL(Framework类型库)类型)。

3、C#的int始终映射到System.Int32。

4、C/C++不将溢出视为错误,允许值回滚;VB总是将溢出视为错误,并且在溢出时抛出异常。

5、checked操作符:

Byte b =100;
b = checked ((Byte) b + 200); //抛出OverflowException异常
b =(Byte) checked(b + 200); //不会抛出异常 

提示:允许发生的溢出使用unchecked语句块包括,可能发生不希望的溢出的部分使用checked语句块包括。所有没有使用checked或unchecked操作符的语句如果发生溢出,都应该记为Bug。

第二节 引用类型和值类型

1、引用类型在使用的时候:内存从托管堆分配、堆上分配的每个对象都有一些额外成员(类型对象指针和同步块索引),这些成员必须初始化、对象中的其他字节总是设为零、可能强制执行垃圾回收。

2、所有结构都是抽象类型System.ValueType的直接派生类。System.ValueType本身又直接从System.Object派生。根据定义,所有值类型必须从System.ValueType派生。所有枚举都从System.Enum派生,后者又从System.ValueType派生。

3、虽然在定义值类型的时候不能为它选择基类型,但是值类型可以实现一个或者多个接口。

4、所有值类型都隐式密封,目的是防止将值类型用作其他引用类型或者值类型的基类型。

提示:如果想要定义一个值类型,则其必须满足:类型具有基元类型的行为、类型不需要从其他任何类型继承、类型也不派生出任何其他类型、类型的实例较小、类型的实例较大但是不作为方法实参和方法的返回值

5、值类型不应该有虚方法,所有方法都不能是抽象的,所有方法都隐式密封。

6、[System.Runtime.InteropServieces.StructLayout(LayoutKind.Auto)]可以让CLR自动排列类或者结构的字段;LayoutKind.Sequential会让CLR保留字段布局;LayoutKind.Explicit利用偏移量在内存中显式排列字段。MicrosoftC#编译器默认为引用类型选择Auto,为值类型选择Sequential

第三节  值类型的装箱和拆箱

1、值类型比引用类类型“轻”,原因是它们不作为对象在托管堆中分配,不被垃圾回收,也不通过指针进行引用。

2、值类型装箱时会发生:在托管堆中分配内存、值类型字段复制到新分配的堆内存、返回对象地址。

3、拆箱不是直接将装箱过程倒过来,拆箱的代价比装箱低得多。拆箱实际就是获取指针的过程。获取装箱对象中各个字段的地址称为拆箱。(获取字段地址之后往往伴随着值的复制,而这个将字段的值从堆复制到栈的过程不在拆箱的概念范畴之内,即拆箱不要求在内存中复制任何字节)

4、在对对象进行拆箱的时候,只能转型为最初未拆箱的值类型

5、  在写代码的时候需要注意选择装箱次数少的写法:

Int32 v = 5 ;
Object o = v ;
V = 123 ;
Console.WriteLine(v + “ , ” + (Int32) o ); //这一步两次装箱
Console.WriteLine(v + “ , ” + o ); //这一步一次装箱
Console.WriteLine(v.ToString + “ , ” + o ); //这一步没有装箱
/*
因为此处使用的WriteLine方法的重载是WriteLine(string)而此处的参数想要创建String的话需要使用
String.Contact( Object arg0, Object arg1, Object arg2);三个参数全部要转换为Object类型
*/

6、很多System下的方法为了减少装箱,提供了很多方法的重载版本,来支持将值类型作为直接的参数。

Int32 n = 100;
Console.WriteLine(n);    //这一行没有装箱

7、调用值类型自己定义的方法的时候不需要装箱。调用值类型重写的虚方法(提示:注意这个虚方法的来源,值类型的基类不能随便选)的时候,如果重写的虚方法用到了基类的实现的时候,值类型会装箱,以便能够使用this指针传递引用。值类型调用非虚的继承的方法的时候无论如何都会进行装箱。(具体示例见P118)

8、Object类的Equals虚方法检测两个引用是否指向同一个对象,换言之,检测的是同一性。

9、在检测对象同一性的时候不应该使用==运算符,虽然Object类型定义了==运算符是检测同一性的,但是可能某个操作数的类型重载了==操作符,赋予了不同“同一性”的语义(个人理解意思就是说可能某个类修改了==运算符的定义,将其定义为相等性的判断了)

10、System.ValueType重写了Object的Equals方法,并且进行了相等性检查,内部的实现顺序为:如果obj实参为null则返回false(注意原因)、如果this和obj实参引用类型不同则返回false、逐字段进行值的比较,如果有字段不相等返回false、返回true;(所以结构体在没有进行Equals方法重写的时候,调用Equals方法检查的均为相等性)

11、ValueType的Equals方法利用反射完成第三步,因为反射自身效率不高,所以自己在定义值类型的时候,最好提供自己的实现,自己在实现Equals方法的时候需要满足:Equals自反(X.Equals(X)必须为true)、对称(X.Equals(Y)和Y.Equals(X)返回相同的值)、可传递(X、Y、Z传递)、一致(XY不变时,结果不变)。

第四节  对象哈希码

1、当自己定义的类型重写Equals方法的时候,还应该重写GetHashCode方法。原因是Hashtable类型、Dictionary类型以及其他的一些集合的实现中,要求两个对象必须具有相同的哈希码才被视为相等。

2、设计GetHashCode方法的时候,应该遵守以下规则:①算法提供良好的随机分布,使哈希表获得最佳性能;②可以调用基类的GetHashCode方法,但是一般不要调用Object或VauleType的GetHashCode方法;③算法至少使用一个实例字段;④理想情况下,算法使用的字段应该不可变;⑤算法执行速度应该尽量快;⑥包含相同值的不同对象应返回相同的哈希码。

提示:如果因为某些原因需要实现自己的哈希表集合,或者要在实现的代码中使用GetHashCode,千万不要对哈希码进行持久化,因为哈希码很容易改变。

第五节  dynamic基元类型

1、C#是类型安全的变成语言。意味着所有的表达式都解析成类型的实例,编译器生成的代码只执行对该类型有效的操作。

2、为了方便开发人员使用反射或者与其他组件通信,C#编译器允许将表达式的类型标记为dynamic,还可以将表达式的结果放到变量中。代码使用dynamic表达式/变量调用成员时,编译器生成特殊的IL代码来描述所需的操作。这中特殊的代码成为payload(有效载荷),在运行时payload代码根据dynamic的实际类型来决定具体的执行操作。示例:

public static void Mian()
{
    dynamic value;
    for (Int32 demo = 0; demo < 2 ; demo++)
    {
        value = (demo == 0) ? (dynamic)5 : (dynamic)"A";
        value = value + value;
        M(value);
    }
}

private static void M(Int32 n){ Console.WriteLine("M(Int32):"+n);}
private static void M(String s){ Console.WriteLine("M(String):"+s);}

/*
输出结果为:
M(Int32): 10
M(String): AA
*/

 3、dynamic的原理:如果字段、方法参数或方法返回值的类型是dynamic,编译器会将该类型转换为Object,并在元数据中向字段、参数或返回类型应用System.Runtime.ComplierServices.Dynamic.Attribute的实例。如果局部变量被指定为dynamic,则变量类型也会成为dynamic,但不会向变量类型引用DynamicAttribute,因为它限制在方法内部使用。综上所述,dynamic其实就是Object,所以方法签名不能仅靠dynamic和Object的变化来区分。(待试验)

4、泛型类、结构、接口、委托或方法的泛型类型实参也可以是dynamic。使用泛型代码时已经编译好的,会将类型视为Object;编辑器不在泛型代码中生成payload代码,所以不会执行动态调度。(待试验)

5、所有表达式都可以隐式转型为dynamic,因为所有表达式最终生成从Object派生的类型。正常情况下,编辑器不允许将表达式从Object类型隐式转换为其他类型,必须显式转型。但是编辑器允许使用隐式转换将表达式从dynamic类型转型为其他类型,虽然这一步允许隐式转型,但是CLR在运行时会验证转型来确保类型安全性。

提示:注意区分var和dynamic。var在声明局部变量的时候只是一种简化的语法,要求编译器根据表达式来推断具体的数据类型。var关键字只能在函数内部声明局部变量。而dynamic可以用于局部变量字段和参数。并且var声明的局部变量必须在声明时显示初始化,而dynamic不需要。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值