CLR via C#-类型基础

一、所有类型都从System.Object派生

运行时要求每个类型最终都从System.Object类型派生,也就是说,以下两个类型定义完全一样

//隐式派生自System.Object
class Employee{}
//显式派生自Object
class Employee:System.Object{}

System.Object类提供了如下所示的公共实例方法,所以每个类型的每个对象都保证了一组最基本的方法。

Equals()//如果两个对象有相同的值,返回true​
GetHashCode()//返回对象值的哈希码
ToString()//返回类型的完整名称
GetType()//返回从Type派生的一个类型的实例,指出调用该方法的对象是什么类型

二、实例化对象时new操作符所做的事情

计算类型及其所有基类型中定义的所有实例字段需要的字节数。堆上每个对象都需要一些额外的成员,包括类型对象指针和同步块索引。CLR利用这些成员管理对象,额外成员的字节数要计入对象大小。

从托管堆中分配类型要求的字节数,从而分配对象的内存,分配的所有字节都设为0。

初始化对象的类型对象指针和同步块索引成员。

④调用类型的实例构造器,传递在new调用中指定的实参。大多数编译器都在构造器中自动生成代码来调用基类构造器。

Employee e=new Employee("ConstructorParam1");

new执行了所有这些操作后,返回指向新建对象一个引用或指针。

如上示例代码中,该引用保存到变量e中,后者具有Employee类型。

实例对象内存的释放

C#没有和new操作符对应的delete操作符,没有办法显式释放为对象分配的内存。

不过CLR采用了垃圾自动回收机制,能自动检测到一个对象不再被使用或访问并自动释放对象的内存。


三、类型转换

类型安全性

CLR最重要的特性之一就是类型安全,调用GetType方法获取对象的确切类型。

由于GetType方法是虚方法,所以一个类型不可能伪装成另一个类型。

例如Employee类型不能重写GetType方法返回一个SuperHero类型。

向基类型转换:隐式转换

C#不要求任何特殊语法即可将对象转换为他的任何基类型,向基类型的转换被认为是一种安全的隐式转换。

向派生类转换:显式转换

将对象转换为他的某个派生类型时,C#只能显式转换,因为这种转换可能在运行时失败

//该类型隐式派生自System.Object
internal class Employee{}
//测试类
Object o=new Employee();//不需要转型,new返回一个Employee对象,而Object是Employee的基类型
Employee e=(Employee)o;//需要转型,Employee派生自Object

在运行时,CLR检查转型操作,确定总是转换为对象的实际类型或者他的任何基类型。

使用C#的is操作符进行转型

is检查对象是否兼容于指定类型。返回Boolean值true或false,is操作符永不抛出异常。

例如以下代码:

Object o=new Object();
Boolean b1=(o is Object);//b1为true
Boolean b2=(o is Employee);//b2为false

如果对象引用null,is操作符总是返回false,因为没有可检查其类型的对象。

is操作符通常这样使用:

if(o is Employee){
  Employee e=(Employee)o;
}

在上述代码中,CLR实际检查两次对象类型,is操作符首先核实o是否兼容与Employee类型。

如果是,在if语句内转型,CLR再次核实o是否引用一个Employee。

使用C#的as操作符进行转型

CLR的类型检查增强了安全性,但无疑会对性能造成一定影响,这是因为CLR首先必须判断变量的引用的对象的实际类型。

CLR必须遍历继承层结构,用每个基类型去核对指定的类型。

由于这是一个常用的编程模式,C#专门提供了as操作符,简化这种代码的写法并提升性能。

Employee e=o as Employee;
if(e!=null){
    //在if语句中使用e
}

在上述代码中,CLR核实o是否兼容于Employee类型。如果是,as返回对同一个对象的非null引用,如果不兼容,返回null。

as操作符使CLR只校验一次对象类型,if语句只检查e是否为null,速度比校验对象的类型快得多。

as操作符的工作方式与强制类型转换一样,只是它永不抛出异常,如果对象不能转型,结果就是null,企图直接使用最终生成的引用会抛出System.NullReferenceException异常。

以下代码对此进行了演示:

Object o = new Object();//新建Object对象
Employee e=o as Employee;//将o转型为Employee
//上述转型会失败,不抛出异常,但e被设为null
e.ToString();//访问e抛出NullReferenceException异常

四、命名空间和程序集

使用using指令简化命名空间

C#编译器通过using指令提供这个机制,例如:

using System.IO;
using System.Text;

只需要在代码中输入FileStream和StringBuilder这两个简化的类型名称,编译器就会自动将引用展开成System.IO.FileStream和System.Text.StringBuilder。

使用using指令创建别名

可能两个或更多类型在不同命名空间中同名。比如使用如下所示代码的引用,二者都有Wifget的类型。

微软强烈建议为类型定义具有唯一性的名称。为了消除歧义,必须显式告诉编译器创建哪个的。

using Microsoft;//尝试附加"Microsoft."前缀
using Wintellect;//尝试附加"Wintellect."前缀

而using指令的另一种形式允许为类型或命名空间创建别名,以消除不同命名空间的重名的类的歧义。

//将WinerllectWidget 符号定义成Wintellect.Widget的别名
using WinerllectWidget = Wintellect.Widget

外部别名

C#还提供了外部别名(extern alias)的功能,来解决命名空间和类型都重名的情况。

外部别名还允许从同一程序集的两个不同版本中访问一个类型

命名空间和程序集的关系

命名空间和程序集(实现类型的文件)不一定相关,特别是,同一个命名空间中的类型不可能在不同程序集中实现。同一个程序集也可能包含不同命名空间的类型。

原文地址:https://www.cnblogs.com/errornull/p/9741276.html