前言

这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享

对象的祖先 – Object

  • 公共方法

    • Equals, 虚方法,对象相等性,默认调用RuntimeHelpers.Equals方法
    • GetHashCode,虚方法,返回哈希吗(随机分布的整数),在哈希表中作为键使用
    • ToString , 默认返回 this.GetType().FullName,调试器默认调用此方法
    • GetType, 返回从Type派生的对象实例,包含Type对象类型元数据信息,和反射配合使用。设计为非虚方法可以防止重写,因为重写会引起欺瞒类型从而破坏类型安全性
  • 受保护的方法
    • MemberwiseClone, 非虚方法,浅拷贝(创建新实例,复制实例字段),感觉是一个鸡肋!
    • Finalize ,GC判断对象为垃圾之后,内存实际回收之前,调用此虚方法,如果需要清理,则重写此方法

new对象的过程

  • 计算字节数(实例字段+额外成员(类型对象指针+同步快索引))
  • 从托管堆中分配响应字节的内存,字节全部设为0
  • 初始化类型对象指针和同步块索引
  • 初始化所有实例字段(0或null)
  • 调用构造函数(同时往上调用直到调用object的构造函数)
  • 返回指向堆中的一个引用

方法调用的过程

  • 调用静态方法

    • CLR定位类型对象
    • 在类型对象方法表中查找与被调用的方法对应的记录项
    • JIT编译(如果需要的话)
    • 执行编译后的本地代码
  • 调用非虚实例方法
    • CLR定位发出调用变量对应的类型对象方法表
    • 如果找到则定位
    • 如果找不到通过类型对象的地址沿着基类回溯查找
    • 定位到方法,然后执行
  • 调用虚实例方法
    • JIT为编译器生成额外的代码
    • 检查定位发出调用变量
    • 通过地址定位发出调用的对象(实际类型)
    • 检查对象类型指针的方法表记录

类型和实例的构建

CLR在进程中运行时,会为System.Type类型创建一个特殊的对象,任何类的类型对象都是该对象的“实例”,他们的类型对象指针成员会初始化成为Type类型对象的引用。Type的类型对象指针指向自己。GetType返回的是指向对象的类型对象指针(一个地址),可以判断对象的真实类型

类型

  • 有符号byte和无符号数值型不符合CLS相容性,如sbyte,ushort,uint,ulong
  • char为16位Unicode字符,decimal为128位高精度浮点值
  • 基元数值类型转换,如果安全(不会丢失数据)则可隐式,否则需要显式转换
  • 小数转整数总是进行截断处理
  • decimal内部的运算都进行了重载
  • checked符号对decimal无效,decimal强制执行安全检查,不安全则抛出异常
  • BigInteger在内部使用UInt32数组表示任意大的整数,没有上限和下限,永远不会造成OverflowException,但是如果值太大,没有足够的内存来改变数组大小,对BigInteger的运算可能抛出OutOfMemoryException异常

引用类型

  • 分配操作:托管堆分配内存,额外成员(类型对象指针和同步索引块)初始化
  • 分配对象时,可能强制执行一次垃圾收集操作
  • 引用类型的复制,只复制内存地址
  • 字段布局默认使用System.Runtime.InteropServices.StructLayoutAttribute.LayoutKind.Auto
  • int x = 4; object o = x; short y = (short)(int)o; // 注意,先拆箱为正确的类型,再进行转型

值类型

  • 轻量类型,没有额外成员,避免了频繁的内存分配节省性能
  • 一般在栈上分配,不需要提供指针引用
  • 不受垃圾回收器控制,所以也缓解了托管堆和GC的压力
  • 枚举从Enum派生,Enum和其他值类型都从ValueType派生
  • 默认隐式密封不可派生,可以实现接口
  • new一个值类型,所以字段初始化位0,如果不new编译器认为字段未初始化
  • 值类型不适合太大(16字节或更小),太大就增加内存拷贝的开销
  • ValueType重写了Equals和GetHashCode方法,默认使用所有字段计算和匹配
  • 值类型建议不可变,字段都标记为readonly比较规范
  • 建议重写自己的GetHashCode和Equals方法,节省性能并且避免装箱
  • 值类型内存回收,不会通过Finalize方法
  • 字段布局默认使用System.Runtime.InteropServices.StructLayoutAttribute.LayoutKind.Sequential
  • 如果不考虑到非托管交互,使用LayoutKind.Auto可以提升性能
  • 值类型没有同步块索引,所以不能使用lock或Monitor让多线程同步对实例的访问
  • 值类型调用基类虚方法会导致装箱,转型为接口会导致装箱

同一性和相等性

  • 同一性使用ReferenceEquals
  • Equals和==通常表示相等性,因为是虚方法,会被重写
  • 使用泛型IEqualable<T>避免装箱拆箱,并且类型安全
  • 建议Equals和GetHashCode一起实现,确保相等性算法和哈希吗算法一致
  • 哈希算法规则:
    • 良好的随机分布,时哈希表获得最佳性能
    • 尽量不适用基类的GetHashCode
    • 算法应该至少使用一个实例字段,并且此字段是不可变的
    • 如果非要获取对象引用的唯一ID,使用RuntimeHelpers的GetHashCode
    • ValueType的GetHashCode使用反射和XOR运算,性能不好,建议重写
    • 哈希码不适合用来做持久化标识,可能产生“哈希碰撞”

Dynamic类型

  • 应用场景
    • 和动态语言交互,如Python或Ruby
    • 和支持IDispatch接口的COM对象通讯
    • 和HTML的DOM进行通讯
  • 忽略编译检查,编译器生成特殊的IL
  • payload,有效载荷,使用运行时绑定器Runtime Binder
  • payload运行时生成动态代码,进入驻留内存的程序集,匿名寄宿DynamicMethods程序集
  • 编译器允许使用隐式转型语法将dynamic和其他类型转型(可能发生装箱拆箱及异常)
  • 允许使用隐式类型复制动态类型,类型推断为dynamic
  • 如果调用返回值为dynamic的方法却返回void,实际值得到null
  • 特定场景,编译器将dynamic转型为特定接口(如IEnumerable和IDisposable)
  • 运行时判断转型结果,如果转型失败,Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
  • dynamic可以简化反射和调用,不过有额外开销,慎用
  • 其他组件动态调用 IDynamicObjectProvider –> GetMetaObject() –> DynamicMetaObject派生类型
  • 如果未实现,则视为C#的Object,运行时进行反射
  • 和动态语言(比如Python)之间传递ExpandoObject对象
 dynamic e = new ExpandoObject();
e.x = ;
e.y = "Feck";
e.z = null; foreach (var v in e)
{
Console.WriteLine("Key={0}, Value={1}", v.Key, v.Value);
} e.Remove("x");

类型和成员基础

  • 友元程序集
    • [assembly:InternalVisibleTo("AssemblyName, PublicKey=...")]
    • 注意:不包含版本和语言文化
  • 接口成员的可访问性都是public,禁止显式指定
  • 派生类可以放宽可访问性,不允许缩紧
  • 静态类
    • 默认 abstract sealed,只继承Object,不允许实现接口
  • 方法调用
    • call指令,静态方法(指定类型)、实例方法(指定对象变量)和虚方法。call指令经常用于非虚的方式调用一个虚方法
    • callvirt指令,此IL指令调用实例方法和虚方法,但不能调用静态方法。调用发出调用对象的实际类型,然后以多态方式调用。由于要检查null条件,所以比call指令稍慢。即使调用非虚的实例方法,也要执行null检查,之所以使用callvirt调用非虚方法,为了执行null检查。C#使用callvirt调用所有的实例方法,调用基类比如base.ToString()会强制使用非虚call指令。如果虚方法调用,则无限递归至栈溢出
    • 由于值类型不为null,也没有多态调用,所以编译器一般都以非虚方式调用值类型的方法
    • 建议尽量避免虚方法,1:性能低   2:不可内嵌(inline) 3:弱化版本控制
  • 常量
    • 使用基元类型定义
    • 隐式为静态成员而不是实例成员
    • 定义常量值将创建元数据,常量值嵌入IL,不会运行时创建内存
    • 常量不会运行时动态引用程序集查找值,如果希望引用查找,使用readonly
  • volatile表示,编译器CLR或硬件不会执行一些“线程不安全”的优化措施
  • 字段
    • 类型字段(静态)和实例字段(非静态)
    • 类型字段,在类型对象中分配,类型对象在类型加载到AppDomain时(引用该类型的任何方法首次进行JIT编译时)创建
    • 对应实例字段,容纳字段数据的动态内存则是在构造类型的一个实例时分配的
    • 注意:可以利用反射来修改readonly字段
  • 方法
    • 类的字段的内联初始化实际上是在构造函数中进行的
    • 内联语法需要考虑性能问题
    • 如果类的abstract,默认构造函数的可访问性就为protected

读书笔记—CLR via C#章节4-7的更多相关文章

  1. 读书笔记—CLR via C#章节3

    这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深 ...

  2. 读书笔记—CLR via C#章节11-13

    前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可 ...

  3. 读书笔记—CLR via C#章节8-10

    前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可 ...

  4. 读书笔记—CLR via C#章节1-2

    这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深 ...

  5. 读书笔记—CLR via C#线程27章节

    前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可 ...

  6. 读书笔记—CLR via C#同步构造28-29章节

    前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可 ...

  7. 读书笔记—CLR via C#线程25-26章节

    前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可 ...

  8. 读书笔记—CLR via C#异常和状态管理

    前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可 ...

  9. 读书笔记—CLR via C#反射

    前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可 ...

随机推荐

  1. C++11于once_flag,call_once分析的实现

    基于该分析llvm的libc++,代替gun的libstdc++,由于libstdc++的代码里太多宏了,看起来蛋疼. 在多线程编程中,有一个常见的情景是某个任务仅仅须要运行一次.在C++11中提供了 ...

  2. 如何关闭CBox(2.4版本号)强制升级的形式

    从今天开始2.4.0.9版本号CBox,提示检测到新的版本号,能够使用后必须更新为新版本号,提示表见下面的例子. 此次升级是强制升级.假如你选择不升级(单击窗体上的升级提示右下角"辍学but ...

  3. 如何使用Maven创建web工程(详细步骤)

    使用eclipse插件创建一个web project 首先创建一个Maven的Project例如以下图 我们勾选上Create a simple project (不使用骨架) 这里的Packing ...

  4. PHP IOS PUSH Demo

    <?php // Put your device token here (without spaces): $deviceToken = '679b466b030038bed6c3a2563c7 ...

  5. Is it always safe to call getClass() within the subclass constructor?(转)

    14down votefavorite   An article on classloading states that the method getClass() should not be cal ...

  6. D - Cow Ski Area

    Description Farmer John's cousin, Farmer Ron, who lives in the mountains of Colorado, has recently t ...

  7. Saiku一个简短的引论

    一个简短的引论 Saiku成立于2008年,通过Tom Barber和Paul Stoellberger研究. 最初叫Pentaho分析工具.最初是基于OLAP4J图书馆的使用GWT采用前端分析工具包 ...

  8. XML概要

     早在两年前,我一直听说XML,但是,只是没有时间去研究它.也不知道它的作用,花了一些时间最近几天来学习他们的语言.是XML的一些简介希望能对各位同学有所帮助: XML是eXtensible Ma ...

  9. 数学思想方法-sasMEMO(17)

    SAS日期及时间格式 data  _null_;input mydate YYMMDD10.;put mydate YYMMDDB10.;put mydate YYMMDDC10.;put mydat ...

  10. Nyoj 虚拟的城市之旅(bfs)

    描述   展馆是未来城市的缩影,个人体验和互动是不变的主题.在A国展馆通过多维模式和高科技手段,引领参观者在展示空间踏上一段虚拟的城市之旅. 梦幻国有N个城市和M条道路,每条道路连接某两个城市.任意两 ...