前言

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

对象的祖先 – 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. hdu 4407 Sum 容斥+当前离线

    乞讨X-Y之间p素数,,典型的纳入和排除问题,列的求和运算总和的数,注意,第一项是最后一个项目数. 如果不改变到第一记录的答案,脱机处理,能保存查询,候,遇到一个操作1,就遍历前面的操作.把改动加上去 ...

  2. My97DatePicker日历控件日报、每周和每月的选择

    My97DatePicker日历控件日报.每周和每月的选择 1.设计源代码 <%@ page language="java" import="java.util.* ...

  3. thinkphp学习笔记6—url模式

    原文:thinkphp学习笔记6-url模式 入口文件是应用的唯一入口,因为可以多入口,每个应用可以对应一个入口文件,系统会从rul参数中解析当前请求的模块,控制器,操作.ThinkPHP是区分大小写 ...

  4. DevExpress.XtraReports.UI.XtraReport 动态报表

    原文:DevExpress.XtraReports.UI.XtraReport 动态报表 using System;using System.Collections.Generic;using Sys ...

  5. 动手学习TCP:数据传输(转)

    前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态. 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的 ...

  6. linux_无密登录

    使用下例中ssky-keygen和ssh-copy-id,仅需通过3个步骤的简单设置而无需输入密码就能登录远程Linux主机. ssh-keygen 创建公钥和密钥. ssh-copy-id 把本地主 ...

  7. js中从blob提取二进制

    文章结构: 一.所遇到的问题 二.解决方法 一. 服务器端通过websocket向浏览器端传输图片(二进制),需要根据不同的图片把图片显示在不同的位置,可行的一个方法是先把图片转化成二进制数组,再把二 ...

  8. SSAS系列——【08】多维数据(程序展现Cube)

    原文:SSAS系列--[08]多维数据(程序展现Cube) 1.引用DLL? 按照之前安装的MS SQLServer的步骤安装完成后,发现在新建的项目中“Add Reference”时居然找不到Mic ...

  9. MVC验证05-自定义验证规则、验证2个属性值不等

    原文:MVC验证05-自定义验证规则.验证2个属性值不等 本文体验2个属性值不等.即当一个属性输入值,另外一个属性输入的值不能和第一个属性值相等.相关文章包括: MVC验证01-基础.远程验证   M ...

  10. AngularJS模块的详解

    AngularJS模块的详解 在讲angularjs的模块之前,我们先介绍一下angular的一些知识点: AngularJS是纯客户端技术,完全用Javascript编写的.它使用的是网页开发的常规 ...