《CLR Via C#》这本书以前就粗略看过两遍,但一直都没能深入理解,而且很多内容也忘记了,现在准备重新看一遍,并将看过的部分写出来,因为写的过程也是一个加深理解的过程。本系列算是学习的一个记录吧,也可以方便以后自己查阅,如果对大家还有些帮助的话,我就很高兴了。书我是选择性的看的,所以顺序和书中的顺序可能不一样。
首先了解下什么是基元类型,基元类型是编译器直接支持的数据类型,像我们平时经常用到的int string bool
都是基元类型,基元类型是映射到FCL
中的类型,像前面的三种类型对应到FCL
中就是Int32 String Boolean
,基元类型只是给我们的编程提供了方便,它和FCL
中的类型在编译后最终生成的IL
完全相同。下面给出基元类型和FCL
类型的对应表
C#基元类型 | FCL类型 | CLS相容 | 说明 |
---|---|---|---|
sbyte | System.Sbyte | ||
byte | System.Byte | ||
short | System.Int16 | ||
ushort | System.UInt16 | ||
int | System.Int32 | ||
uint | System.Uint32 | ||
long | System.Int64 | ||
ulong | System.Uint64 | ||
char | System.Char | ||
float | System.Single | ||
double | System.Double | ||
bool | System.Boolean | ||
decimal | System.Decimal | ||
object | System.Object | ||
string | System.String |
我们平时在写程序时通常都是使用基元类型,因为方便,但本书的作者认为应该直接使用FCL
中的类型,原因如下:
-
在c#中
long
对应的FCL类型为System.Int64
,但是在其他的语言中,如c++ 就将long
视为一个Int32
。这样习惯于一种编程语言的人在看另一种编程语言写的源码是就会产生错误的理解。 -
在
FCL
中的许多的方法将类型名作为方法名的一部分,如System.Convert
类提供ToBoolean,ToInt32,ToSingle
等。这样在使用基元类型做类型转换时就感觉有点怪怪的,如:float val=Convert.ToSingle(“23”);
值类型和引用类型的区别
- 所有的值类型都是从
System.TypeValue
派生,如Struct Enum
都是值类型;所有引用类型都是从System.Object
派生。 - 值类型分配在线程堆栈上,引用类型分配在托管堆上。
- 值类型表现形式有未装箱形式和已装箱形式,而引用类型总是已装箱形式。
- 所有的值类型都是
sealed
类型,所以值类型不能作为任何类型的基类型,也不能在值类型中引入虚方法。 - 创建一个引用类型变量时,会被初始化为
null
,试图使用一个null
的引用类型变量时,会抛出NullReferenceException
异常,也就是常见的“未将对象引用设置到对象的实例”;值类型在创建时所有成员都初始化为0
了,所以不会抛出该异常。
原文例子表示值类型和引用类型的区别
class Program
{
static void Main(string[] args)
{
SomeRef r1 = new SomeRef();
SomeVal v1 = new SomeVal();
r1.x = 5;
v1.x = 5;
Console.WriteLine(r1.x); //5
Console.WriteLine(v1.x); //5
SomeRef r2 = r1;
SomeVal v2 = v1;
r1.x = 8;
v1.x = 9;
Console.WriteLine(r1.x); //8
Console.WriteLine(r2.x); //9
Console.WriteLine(v1.x); //9
Console.WriteLine(v2.x); //5
}
}
class SomeRef
{
public Int32 x;
}
struct SomeVal
{
public Int32 x;
}
CLR
中类型字段布局的控制
为了提高性能,clr
能按照所选择的任何方式来排列类型的字段。我们可以通过在类会是结构上使用System.Runtime.InteropServices.StructLayoutAttribute
属性来改变这种排列的顺序。该属性接受一个LayoutKind
的枚举值(Auto,Sequential,Explicit
),默认情况下C#编译器会为类选择Auto
,为结构选择Sequential
。下面来定义一个类和一个结构
struct SomeVal
{
public String name;
public Int32 id;
}
class SomeRel
{
public String name;
public Int32 id;
}
查看IL代码可以看到默认的排列顺序
现在给类和结构加上属性,首引用加命名空间 using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Auto)]
struct SomeVal
{
public String name;
public Int32 id;
}
[StructLayout(LayoutKind.Sequential)]
class SomeRel
{
public String name;
public Int32 id;
}
再查看IL
可以看到顺序已经发生了变化。
C#编译器总是会默认给结构这样的值类型选择Sequential
,是因为值类型会经常和非托管代码进行交互,字段必须保持和开发人员定义的一致。我们在编码过程中如果判断所创建的值类型不会与非托管代码进行交互,就可以像上面那样给创建的值类型添加[StructLayout(LayoutKind.Auto)]
,让其进行自动排列,以提高性能。