类型基础
所有类型都从System.Object派生
CLR要求所有对象都用new 操作符来创建。
Employee e = new Employee("Constructor Parameters");
以下是 new 操作符所做的事情:
#1, 计算类型及所有基类型(一直到System.Object, 虽然它没有定义自己的实例字段)中定义的所有实例字段需要的字节数。
堆上的每个对象还需要一些额外(overhead 开销成员)的成员 -- 即“类型对象指针”(type object pointer)和“同步块索引”(sync block index)。这些成员由CLR用于管理对象。这些额外成员的字节数会计入对象大小。
#2, 它从托管堆中分配指定类型要求的字节数,从而分配对象的内存,分配的所有字节都设为0。
#3, 它初始化对象的“类型对象指针”和“同步块索引”成员。
#4, 调用类型的实例构造器,向其传入在对new 的调用中指定的任何实参。大多数编译器都在构造器中自动生成代码来调用一个基类构造器。每个类型的构造器在调用时,都要负责初始化由这个类型定义的实例字段。最总调用的是System.Object的构造器,该构造器只是简单地返回,不会做其他任何事情。
new执行了所有操作之后,会返回指向新建对象一个引用(或指针)。在前面的示例代码中,这个引用会保存到变量e中,后者具有Employee类型。
类的“新实例”和“实例成员”:两种不同的“实例”。一种是类的实例,也就是具体的对象。另一种是类中定义的实例字段。所谓“实例字段”,就是指非静态字段,有时也称为“实例成员”。简单地说,实例成员是属于类的对象的,
而静态成员是属于类的。
类型安全
CLR总是知道一个对象(某个类型的实例)是什么类型。所有表达式都解析成某个类型的实例,在编译器生成的代码中,只会执行对这个类型来说有效的操作。与非类型安全的语言相比,类型安全的语言的优势在于:程序员会犯的许多错误能在编译时检测到,确保代码在你的尝试执行它之前是正确的。除此之外,编译时语言通常能生成更小、更快的代码,因为他们能在编译时进行更多的假设,并在生成的IL和元数据中落实那些假设。
类型转换
CLR最重要的特性之一就是类型安全性。在运行时,CLR总是知道一个对象是什么类型。调用GetType() 方法,总是知道一个对象确切的类型是什么。
开发中,开发人员会经常将一个对象从一种类型转换为其他各种类型。CLR允许将一个对象转换为它的(实际)类型或者它的任何基类型。
#1: 向基类型的转换被认为是一种安全的隐式转换。
#2: 将对象转换为它的某个派生类型时,C#要求只能进行显式转换,因为这样的转换有可能在运行时失败。在运行时,CLR检查类型操作,确定总是转换为对象的实际类型或者它的任何基类型。
这就是类型安全的设计。如果CLR允许这样的转型,就无类型安全性可言了,将出现难以预料的结果 -- 其中包括应用程序崩溃,以及安全漏洞的出现(因为一种类型能轻松地伪装成另一种类型)。
类型伪装是许多安全漏洞的根源,它还会破坏应用程序的稳定性和健壮性。类型安全是CLR一个重要的目标。
is 操作符,检查一个对象是否兼容于指定的类型,并返回一个Boolean值;true或false。注意 is 操作符永远不会抛出异常。如果对象引用是null,is操作符总是返回false。
Object o = new Object();
if (o is Employee)
{
Employee e = (Employee)o;
}
这段代码中,CLR实际上会检查两次对象的类型。
CLR 的类型检查增强了安全性,但无疑也会对性能造成一定影响。因为CLR首先必须判断变量引用的对象的实际类型。然后,CLR必须遍历继承层次结构,用每个基类型去核对指定的类型(如Employee)。
上面这个事一个相当常用的编程模式,所以C#专门提供了as操作符,目的就是简化这种代码的写法,同时提升性能。
Employee e = o as Employee;
if(e != null)
{
//.....
}
as 操作符的工作方式与强制类型转换一样,只是它永远不会抛出一个异常 -- 相反,如果对象不能转型,结果就是null。所以,正确做法也就是检查最终生成的引用是否为null。应该不要直接使用最终生成的引用,否则可能会抛出一个System.NullReferenceException 异常。
注意:C#允许在一个类型中定义转换操作符方法。只有在使用一个转型表达式时,才会调用这些方法;使用C#的as或者is操作符时,永远不会调用他们。
命名空间 (namespace)
用于对相关的类型进行逻辑性分组,开发人员可以使用命名空间方便地定位一个类型。例如,System.Text命名空间定义了一组执行字符串处理的类型。
using 指令指示编译器为每一个类型附加不同的前缀,直到找到一个匹配项。using的使用,不仅极大地减少打字量,还有助于增强代码的可读性。
using指令还支持另一种形式,允许为一个类型或者命名空间创建别名。如果只想使用一个命名空间中的少数几个类型,不希望它的所有类型都跑出来“污染”全局命名空间,别名就显得十分方便。
using System;
using jack = CSI.Widget;
重要提示:CLR并不知道命名空间的任何事情。访问一个类型时,CLR需要直到类型的完整名称(可能是一个相当长的、包含句点符号的名称)以及该类型的定义具体在哪一个程序集中。这样一来,“运行时”才能加载正确的程序集,找到目标类型,并对其进行操作。
编译器会扫描引用的所有程序集,在其中查找类型的定义。一旦找到正确的程序集,程序集信息和类型信息就会嵌入最终生成的托管模块的元数据中。为了获取程序集信息,必须将定义了“引用的类型”的程序集传给编译器。
默认情况下,C#编译器会自动在MSCorLib.dll 程序集中查找 “引用的类型”,即使你没有显式告诉它这样做。MSCorLib.dll程序集中包含了所有核心Framework类库(FCL)类型的定义,比如Object, Int32, String等。
命名空间和程序集(实现了一个类型的文件)不一定是相关的。特别是,同一个命名空间中的各个类型可能是在不同的程序集中实现的。在一个程序集中,也可能包含不同命名空间中的类型。
运行时的相互关系
类型、对象、线程栈和托管堆在运行时的相互关系。调用静态方法、实例方法和虚方法的区别。
已经加载了CLR的一个Microsoft Windows 进程。这个进程中,可能存在多个线程。一个线程的创建时,会分配到一个1MB大小的栈。这个栈的空间用于向方法传递实参,并用于方法内部定义的局部变量。栈是从高位内存地址向低位内存地址构建的。
栈帧(stack frame)代表的是当前线程的调用栈中的一个方法调用。在执行线程的过程中进行的每个方法的调用都会在调用栈中创建并压入一个stack frame
至此我们讨论了源代码、IL和JIT编译的代码之间的关系,还讨论了线程栈、实参、局部变量以及这些实参和变量如何引用托管堆上的对象。我知道了,对象中包含一个指针,它指向对象的类型对象(类型对象中包含静态字段和方发表)。
还讨论了JIT编译器如何决定静态方法、非虚实例方法以及虚实例方法的调用方式。这一切的理解,可以帮助深刻地认识CLR的工作方式。
注意,Employee和Manager类型对象都包含“类型对象指针”成员。这是由于类型对象本质上也是对象。CLR创建类型对象时,必须初始化这些成员。初始化成什么呢?CLR开始在一个进程中运行时,会立即为MSCorLib.dll中定义的System.Type类型创建一个特殊的类型对象。Employee和Manager类型对象都是该类型的“实例”。因此,它们的类型对象指针成员会初始化成对System.Type类型对象的引用,如下面所示。
-----------------------------------------------------------
- CLR via C#深解笔记六 - 泛型
面向对象编程一个好处就是“代码重用”,极大提高了开发效率.如是,可以派生出一个类,让它继承基类的所有能力,派生类只需要重写虚方法,或添加一些新的方法,就可以定制派生类的行为,使之满足开发人员的需求. ...
- CLR via C#深解笔记四 - 方法、参数、属性
实例构造器和类(引用类型) 构造器(constructor)是允许将类型的实例初始化为良好状态的一种特殊方法.构造器方法在“方法定义元数据表”中始终叫.ctor. 创建一个引用类型的实例时: #1, ...
- CLR via C#深解笔记三 - 基元类型、引用类型和值类型 | 类型和成员基础 | 常量和字段
编程语言的基元类型 某些数据类型如此常用,以至于许多编译器允许代码以简化的语法来操纵它们. System.Int32 a = new System.Int32(); // a = 0 a = 1 ...
- CLR via C#深解笔记七 - 自动内存管理(垃圾回收)
每个应用程序都要使用这样或者那样的资源,比如文件.内存缓冲区.屏幕空间.网络连接.数据库资源等.事实上,在面向对象的环境中,每个类型都代表可供程序使用的一种资源. 要使用这些资源,必须为代表资源的类型 ...
- CLR via C#深解笔记五 - 事件
事件处理实际上是一种具有特殊签名的delegate, 像这个样子:public delegate void EventHandler(object sender, EventArgs e); 类型 ...
- CLR via C#深解笔记一 - CLR & C# 基础概念
写在前言 .Net Framework并不是Win 32 API 和COM上的一个抽象层. 某种程度上,它是自己的操作系统,有自己的内存管理器,自己的安全系统,自己的文件加载器,自己的错误处理 ...
- python3.4学习笔记(二) 类型判断,异常处理,终止程序
python3.4学习笔记(二) 类型判断,异常处理,终止程序,实例代码: #idle中按F5可以运行代码 #引入外部模块 import xxx #random模块,randint(开始数,结束数) ...
- C# 《编写高质量代码改善建议》整理&笔记 --(五)类型设计
1.区分接口和抽象类的应用场合 区别: ①接口支持多继承,抽象类则不能. ②接口可以包含方法,属性,索引器,事件的签名,但不能有实现,抽象类则可以. ③接口在增加新方法后,所有的继承者都必须重构,否则 ...
- 《CLR.via.C#第三版》第二部分第4,5章节读书笔记(二)
这两章全是理论性的东西,我觉得不必过于钻牛角尖.理论这东西,只有在长期的实践中去慢慢领悟才会深刻.下面我只写些我认为重要的关键知识. (一)类型转换 知识点:向基类型的转换被认为是一种安全的隐式转换: ...
随机推荐
- [51单片机] EEPROM AT24c02 [存储\读取一个字节]
/*----------------------------------------------- 名称:IIC协议 EEPROM24c02 存数读取数据 内容:此程序用于检测EEPROM性能,测试方 ...
- Entity Framework后台采用分页方式取数据与AspNetPager控件的使用
本文是一个对AspNetPager控件使用的笔记! 有关AspNetPager控件可以查看杨涛主页.这是一个开放的自定义ASP.NET控件,支持各种自定义的数据分页方式,使用很方便,而且功能也很强大, ...
- C# VS JAVA 差异 (未完待续)
1. 静态构造函数 C#中有静态构造函数, Java中没有静态构造函数.其实Java中有一个类似静态构造函数的东东,称作静态初始化,或者静态代码块,可以通过这样的代码实现相同的功能: 但是Java中静 ...
- [Python爬虫] Selenium+Phantomjs动态获取CSDN下载资源信息和评论
前面几篇文章介绍了Selenium.PhantomJS的基础知识及安装过程,这篇文章是一篇应用.通过Selenium调用Phantomjs获取CSDN下载资源的信息,最重要的是动态获取资源的评论,它是 ...
- mysql 5.7 win7 压缩版安装
1.下载mysql压缩版并解压: 2.复制my-defualt.ini , 命名为my.ini; 3. 3.1 运行在下图bin目录下运行:mysqld --install 安装mysql服务: ...
- atitit.软件开发方法总结O6
atitit.软件开发方法总结O6 #--cmm/cmmi 都晓得这个. #--IPD集成产品开发 结构化的流程 IPD工具:包括业务及技术上的共工具. 5.考评:包括团队和个人绩效考核两个方面:首 ...
- 公钥,私钥,SSL(讲的很生动) (转) 对称加密、非对称加密初探
最近开始做消息推送,有不少概念性的东西需要知道,首先应该了解的是密钥.这片文章很清晰的讲解了对称密钥.非对称密钥.ssl的知识. 原文地址:http://chenling1018.blog.163.c ...
- QT on Android开发
1.安装QT 2.安装JDK 配置如下系统环境变量: JAVA_HOME D:\Java\jdk Path %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin CLASSPATH ...
- Clojure上手
Clojure,这是什么鬼?一门基于JVM(现在也有基于.NET CLR的了:Clojure CLR) 的函数式编程语言.在JVM平台运行的时候,会被编译为JVM的字节码进行运算..为什么要学它?其设 ...
- Docker实践(6)—CentOS7上部署Kubernetes
Kubernetes架构 Kubernetes的整体架构如下: Master为主控节点,上面运行apiserver,scheduler,controller-manager等组件.Minion相当于工 ...