学习CLR via C#(第4版)(三)- 基元类型、引用类型和值类型

基元类型、引用类型和值类型

5.1编程语言的基元类型

FCL(Framework Class Library) Framework 类库,FCL是 .net Framework 包含的一组DLL程序集的统称,FCL包含了提供了很多功能,它是生成.NET Framework 应用程序、组件和控件的基础。FCL由命名空间组成。每个命名空间都包含可在程序中应用的类、结构、委托和接口等。常见的命名空间有System、System.Windows等。

什么是基元类型

编译器直接支持的数据类型称为基元类型(primitive type)。基元类型直接映射到Framework类库(FCL)中存在的类型。比如以下4行代码都是正确的,生成的IL代码也是相同的。

int a = 0;
System.Int32 a =0;
int a = new int();
System.Int32 a = new System.Inte32();

下表列出了FCL类型在C#中对应的基元类型:

C#基元类型FCL类型符合CLS说明
sbyteSystem.SByteNO有符号8位值
byteSystem.ByteYES无符号8位值
shortSystem.Int16YES有符号16位值
ushortSystem.UInt16NO无符号16位值
intSystem.Int32YES有符号32位值
uintSystem.UInt32NO无符号32位值
longSystem.Int64YES有符号64位值
ulongSystem.UInt64NO无符号64位值
charSystem.CharYES16位Unicode字符
floatSystem.SingleYESIEEE32位浮点值
doubleSystem.DoubleYESIEEE64位浮点值
boolSystem.BooleanYES一个true/false值
decimalSystem.DecimalYES一个128位高精度浮点值,常用于不容许舍入误差的金融计算
stringSystem.StringYES一个字符数组
objectSystem.ObjectYES所有类型的基类型
dynamicSystem.ObjectYES对于CLR,dynamic和object完全一致。然而,C#编译器允许使用一个简单的语法,让dynamic变量参与动态调度

可以假定C#编译器自动假定在所有的源代码文件中添加了以下using指令:

using sbyte = System.Sbyte;
using byte = System.Byte;
using int = System.Int32;
using uint = System.UInt32;
......

表达式由字面量构成,编译器在编译的时候就能完成表达式求值

Boolean found=false;//生成的代码将found设为0
Int32 x=100+20+3;//x设为123
String a="a"+"bc";//s设为“abc”

checked和unchecked基元类型操作

此指令就用来检查溢出和不检查溢出,而默认是unchecked,不过这个可以改。检查溢出就报异常,不检查溢出就回滚。

对基元类型执行许多算数运算都可能造成溢出。不同语言处理溢出也是不同的。C和C++不将溢出视为错误,并允许值回滚;应用程序"若无其事"的运行着。相反,Microsoft Visual Basic总是将溢出视为错误,并会抛出异常。

CLR提供了一些特殊的IL指令,允许编译器选择它认为最恰当的行为。CLR有一个add指令,将两值相加但不检查溢出。还有一个add.ovf指令,作用是两值相加,溢出时抛出异常。类似的还有sub/sub.ovf等。

C#允许开发人员自己决定如何处理溢出。溢出检查默认是关闭的。开发人员可以使用C#编译器控制溢出的一个办法是使用/checked+编译器开关。

C#通过提供checked和unchecked操作符来实现局部是否检查发生溢出。

unchecked:
UInt32 invalid = unchecked((UInt32)(-1));    //OK
checked:
Byte b = 100;
b = checked((Byte)(n+200))//抛出溢出异常 
  C#还提供checkedunchecked语句

checked{
    Byte b = 100;
    b = checked((Byte)(n+200))}

在Visaul Studio的"高级生成设置"对话框中可以指定编译器是否检查溢出。

System.Decimal类型是一个非常特殊的类型。虽然C#将Decimal视为一个基元类型,但CLR则不然,也就是说CLR没有相应的IL指令来决定如何处理Decimal值。Decimal值得处理速度是要慢于其他CLR基元类型的值得处理速度。还有对Decimal来说,checked和uncheked操作符、语句和编译器都是无效的,Decimal溢出时是一定会抛出异常的

5.2引用类型和值类型

CLR支持两种类型:引用类型和值类型。

值类型

虽然FCL中大多数都是引用类型,但开发人员用的最多的还是值类型。引用类型总是在托管堆上分配的,C#的new操作符会返回对象的内存地址——也就是指向对象数据的内存地址。

使用引用类型必须注意到一些性能问题,首先考虑一下事实:

1)内存必须从托管堆上分配。

2)堆上分配的每个对象都有一些额外的成员(比如前面提到过得"类型对象指针"和"同步块索引"),这些成员必须初始化。

3)对象中的其他字节(为字段而设)总是设为零。

4)从托管堆上分配一个对象时,可能强制执行一次垃圾回收操作。

如果所有类型都是引用类型,应用程序的性能会显著下降。为了提升简单的、常用的类型的性能,CLR提供了名为**“值类型”**的轻量型类型。

值类型的实例一般在线程栈上分配的(虽然也可作为字段嵌入一个引用类型的对象中)。在代表值类型的实例的一个变量中,并不包含一个指向实例的指针。相反,变量中包含了实例本身的字段。

由于变量已经包含了实例的字段,所以为了操作实例中的字段,不再需要提供一个指针。值类型的实例不受垃圾回收器的控制。因此,值类型的使用缓解了托管堆中的压力,并减少了一个应用程序在其生存期内需要进行的垃圾回收次数

.NET Framework SDK文档明确指出,在查看一个类型时,任何称为"类"的类型都是引用类型。如System.Exception类、System.Random类等引用类型。文档将所有值类型都称为结构或枚举。如System.Int32结构、System.Boolean结构等值类型。

所有值类型都必须从System.ValueType派生。所有枚举类型都从System.Enum抽象类派生,而System.Enum又是从System.ValueType派生的。CLR和所有编程语言都给予枚举特殊待遇,以后会提到。

所有值类型都是隐式密封的(sealed),目的是防止将一个值类型用于其他任何引用类型或值类型的基类型

在托管代码中,要由定义类型的开发人员决定在什么地方分配类型的实例,使用该类型的人对此并无控制权。

//引用类型
class SomeRef 
{ 
    public Int32 x; 
} 
//值类型
struct SomeVal 
{ 
    public Int32 x; 
} 
 
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);                //8 
    Console.WriteLine(v1.x);                //9 
    Console.WriteLine(v2.x);                //5 
}

在这里插入图片描述

建议条件

除非以下条件都能满足,否则不应该将一个类型声明成值类型:

1)类型具有基元类型的行为。

2)类型不需要从其他任何类型继承

3)类型也不会派生出其他类型。

类型实例的大小应该在考虑之列,因为默认情况下,实参是以传值方式传递的,这会造成对值类型实例中的字段进行复制,从而影响性性能。同样的,被定义为返回一个值类型的一个方法在返回时,实例中的字段会赋值到调用者分配的内存中,从而影响性能。

所以,选用值类型还应满足:

1)类型的实例较小(约16字节或者更小)

2)类型的实例较大(大于16字节),但不作为方法的实参传递,也不从方法返回。

值类型的主要优势在于它们不作为对象在托管堆上分配。

值类型和引用类型的区别:

1)值类型对象有两种表示形式:未装箱(unboxed)和已装箱(boxed)。引用类型总是处于已装箱形式

2)值类型是从System.ValueType派生的。该类型提供了与System.Object定义的相同的方法。然而,System.ValueType重写了Equals方法和GetHashCode方法。由于这个默认实现存在性能问题,所以定义自己的值类型时,应该重写Equals和GetHashCode方法,并提供它们的显示实现。

3)值类型的所有方法都不能是抽象的,而且所有方法都是隐式密封(sealed)方法。

4)引用类型的变量包含的是堆上的一个对象的地址。默认情况,在创建一个引用类型的变量时,它被初始化为null,表明引用类型的变量当前不指向一个有效对象。相反,值类型初始化是,所有的成员都会初始化为0。由于值类型的变量不是指针,所以在访问一个值类型时,不会抛出NullReferenceException异常。CLR确实提供了一个特殊的特性,能为值类型 添加"可空"标识。如"int?"

5) 将一个值类型的变量赋给另一个值类型变量,会执行一次逐字段复制。将引用类型赋给另一个引用类型时,只复制内存地址。

6)由于未装箱的值类型不再堆上分配,所以一旦定义了该类型的一个实例的方法不再处于活动状态,为他们分配的内存就会被释放。这意味着值类型的实例在其内存被回收时,不会通过Finalize方法接收到一个通知。

引用类型和值类型的区别:

引用类型值类型
从托管堆中分配从线程的堆栈中分配
对象考虑垃圾回收机制不考虑垃圾回收机制
所有类都是引用结构或枚举都是值类型
System.Object继承自System.ValueType
只有装箱形式有两种形式:装箱和未装箱
可以继承和派生不能作为基类,不能有虚方法
引用类型变量初始化时默认为null初始化时默认为0值
复制时只拷贝内存地址复制时“字段对字段”的拷贝

何时使用struct, 何时使用class?

同时满足以下三个条件:

**1)**类型中没有成员会会修改类型的实例字段;

**2)**类型不需要从其它任何类型继承;

3)类型不会派生出其他任何类型;

并满足以下两个条件中的一个:

**1)**类型的实例小于16字节;

**2)**类型的实例大于16字节,但不作为方法的实参传递,也不作为方法的返回值。此时,可以把这个类型定义为struct, 否则定义为class.

定义一个值类型应该注意什么?

1)由于System.ValueType重写了Equals方法和GetHashCode方法,在定义自己的值类型时,也要重写这两个方法并提供它们的显式实现;

**2)**所有方法都不能是虚方法。

SomeVal为一个struct,包含一个实例字段x, 以下代码有何不同?

SomeVal v = new SomeVal();
SomeVal v;

两行代码都会在线程栈上分配内存空间,唯一的不同在于C#会认为new操作符对v进行了初始化。如果用第一行代码定义v, 打印v.x时会打印出默认值0, 如果使用第二行代码定义v, 打印v.x时会出错。

5.3值类型的装箱和拆箱

装箱过程

在CLR中为了将一个值类型转换成一个引用类型,要使用一个名为装箱的机制。

将值类型转换为引用类型。当我们把值类型参数传递给需要引用类型参数的方法时,会自动进行装箱操作。过程如下:

  • 从托管堆为要生成的引用类型分配大小。大小为:值类型实例本身的大小+额外空间(类型对象指针同步块索引SyncBlockIndex)。
  • 值类型的字段复制到新的分配的堆内存
  • 返回托管堆中新分配内存的地址。也就是指向对象的引用。

拆箱过程

拆箱不是直接将装箱过程倒过来。拆箱的代价比装箱低得多。拆箱其实就是一个获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段)。事实上,指针指向的是已装箱实例中的未装箱部分。所以,和装箱不同,拆箱不要求在内存中复制字节。还有一个重点就是,拆箱之后,往往会紧接着发生一次字段的复制操作。

获取指向对象中包含的值类型部分的指针。一般拆箱之后会进行字段拷贝操作,两个操作加起来才是真正与装箱互反的操作。过程如下:

  • 如果引用为Null,则抛出NullReferenceException异常。
  • 如果引用对象不是一个期望值类型的已装箱对象,会抛出InvalidCastException异常。
  • 返回一个指向包含在已装箱对象中值类型部分的指针。

上面第二条意味着一下代码不会如你预期的那样工作:

public static void Main(){
    Int32 x = 5;
    Object o = x;
    Int16 y = (Int16) o;//抛出InvalidCastException异常
}

在对一个对象进行拆箱的时候,只能将其转型为原始未装箱时的值类型——Int32,下面是正确的写法:

public static void Main(){
    Int32 x = 5;
    Object o = x;             //对x进行装箱,o引用已装箱的对象 
    Int16 y = (Int16) (Int32) o; //先拆箱为正确的类型,在进行装箱
}

前面说过,在进行一次拆箱后,经常会紧接着一次字段的复制。以下演示了拆箱和复制操作:

public static void Main() {  
    Point p = new Point(); //栈变量
    p.x = p.y = 1;    
    object o = p;          //对p进行装箱,o引用已装箱的实例
    p = (Point) o;         //对o进行拆装,将字段从已装箱的实例复制到栈变量
}

在最后一行,C#编译器会生成一条IL指令对o进行拆箱,并生成另一条IL指令将这些字段从堆复制到基于栈的变量p中。

再看看一下代码:

public static void Main() {  
    Point p = new Point();      // 栈变量
    p.x = p.y = 1;    
    object o = p;               // 对p进行装箱,o引用已装箱的实例

    // 将Point的x字段变成2
    p = (Point) o;              // 对o进行拆装,将字段从已装箱的实例复制到栈变量
    p.x = 2;                    // 更改变量的状态
    o = p;                      // 对p进行装箱,o引用已装箱的实例
}    

最后三行代码唯一的目的就是将Point的x字段从1变成2.为此,首先要执行一次拆箱,在执行一次字段复制,在更改字段(在栈上),最后执行一次装箱(从而在托管堆上创建一个全新的已装箱实例)。希望你能体会到装箱和拆箱/复制操作对应用程序性能的影响。

在看个演示装箱和拆箱的例子:

private static void Main(string[] args)
{
     Int32 v = 5;            // 创建一个伪装箱的值类型变量
     Object o = v;           // o引用一个已装箱的、包含值5的Int32
     v = 123;                // 将未装箱的值修改成为123
     Console.WriteLine(v + "," + (Int32)o);  //显示"123,5"
}

你可以看出上述代码进行了几次装箱操作?是3次

主要原因是在Console.WriteLine方法上。

Console.WriteLine方法要求获取一个String对象,为了创建一个String对象,C#编译器生成的代码来调用String对象的静态方法Concate。该方法有几个重载的版本,唯一区别就是参数数量,在本例中需要连接三个数据项来创建一个字符串,所以编译器会选择以下Concat方法来调用:

public static String Concat(Objetc arg0, Object arg1, Onject arg2);

所以,如果像下面写对WriteLine的调用,生成的IL代码将具有更高的执行效率:

Console.WriteLine(v + "," + o);  //显示"123,5"

这只是移除了变量o之前的(Int32)强制转换。就避免了一次拆箱和一次装箱。

我们还可以这样调用WriteLine,进一步提升上述代码的性能:

Console.WriteLine(v.ToString() + "," + o);  //显示"123,5"

现在,会为未装箱的值类型实例v调用ToString方法,它返回一个String。String类型已经是引用类型,所以能直接传给Concat方法,不需要任何装箱操作。

装箱与拆箱简单总结

装箱就是把本来在栈中的值类型,在堆中新开辟一个内存空间,把值类型的数据复制进去,并增加引用类型都有的类型指针和同步块索引,然后返回这个内存空间引用地址。

拆箱就是反过来,先获取装箱对象中各个字段的地址,再将这些字段包含的值从堆复制到栈。

由上面看出装箱拆箱其实很影响效率,所以写代码的时候应该避免。

另外装箱的值类型,调用自身的函数修改自己的字段的时候并不会修改堆里的数据,只会先转换为值类型,再修改栈里的数据。

如果要修改堆里的数据,只能定义一个接口,让值类型里的函数去实现这个接口,然后想修改堆里的数据,那么就把对象转换为这个接口,那么去修改的话,就会修改堆里的数据。

如果你看不懂上面的话,那么请慎用值类型,作者推荐不要定义任何会修改实例字段的属性或者方法,甚至可以将值类型的所有字段都加上readonly。

其实结构体什么的用类就好了,大的结构体传参啊什么的,搞不好还会引起栈溢出。毕竟每个线程也就1MB的栈空间。

一个值类型调用System.Object类定义的方法会不会发生装箱?

如果值类型重写了System.Object定义的虚方法(Equals, GetHashCode, ToString),调用时不会发生装箱,如果重写的方法中调用了基类的实现,则需要进行装箱;如果值类型调用了非虚方法(GetType, MemberwiseClone),则会发生装箱。

对象相等性和同一性

System.Object类型提供了一个名为Equals的虚方法,它的作用是在两个对象包含相同的值得前提下返回true。如:

public class Object{
    publick virtual Boolean Equals(Object obj) {
        //如果两个引用指向同一个对象,它们肯定包含相同的值
        if ( this == obj ) return true;
        //假定对象不包含相同的值
        return false;
    }
}

对于Object的Equals方法的默认实现来说,它实现的实际是同一性,而非相等性。

下面展示了如何在内部正确实现一个Equals方法。

1)如果obj实参为null,就返回false,因为在调用非静态的Equals方法时,this所标识的当前对象显然不为null.

2)如果this和obj实参引用同一个对象,就返回true。在比较包含大量字段的对象时,这一步有助性能提升。

3)如果this和obj实参引用不同类型的对象,就返回false。一个String对象显然不等于一个FileStream对象。

4)针对类型定义的每个实例字段,将this对象中的值与obj对象中的值进行比较。任何字段不相等,就返回false。

5)调用基类的Equals方法,以便比较它定义的任何字段。如果基类的Equals方法返回false,就返回false;否则返回true;

例如:

public class Object{
    public virtual Boolean Equals(Object obj) {
        //要比较的对象不能为null
        if (obj == null ) return false;
        //如果对象类型不同,则肯定不相等
        if (this.GetType() != obj.GetType()) return false;
        //如果对象属于相同的类型,那么在它们所有字段都匹配的前提下返回true
        //由于System.Object没有定义任何字段,所以字段是匹配的
        return true;
    }
}

由于,一个类型能重写Object的Equals方法,所以不能再调用这个Equals方法来测试同一性。为了修正这一问题,Object提供了一个静态方法ReferenEquals,其原型如下:

public class Object{
    public static Boolean ReferenceEquals(Object objA , Object objB) {
        retuen ( onjA == objB );
    }
}

如果想检查同一性,务必调用ReferenceEquals,而不应该使用C#的== 操作符,因为 == 操作符可能被重载

System.ValueType(所有值类型的基类)重写了Object的Equals方法,并进行了正确的实现来执行值得相等性检查。

对象的相等性和同一性简单总结

之前我们讲过System.Object提供了名为Equals的虚方法,也就是说所有的对象都是有的,作用实在两个对象包含相同值的前提下返回true。

然而这个方法只是比较了同一性,而不是相等性。

实际上就是对应的同一性就是指两个对象的引用相同,也就是说它们指向同一个对象。

相等性如字面意思可知。也就说如果具备同一性,那么一定具备相等性。

由于Equals是个虚方法,可以重写,所以并不一定就是这个用法。

(System.ValueType就重写了,Equals实现的是相等性而不是统一性。但是这个Equals里的实现步骤用到了反射,而反射这个东西又是比较慢的,所以定义自己的值类型时可以考虑重写,从而提高性能。)

而==这个操作符也是可以重载的,除非你在比较之前,将两个对象的类型都转换为object。

于是Object又提供了一个静态方法,Object.ReferenceEquals,效果就如上所述,比较同一性。

注意,如果自己去定义一个值类型,然后重写Equals方法去实现相等性。那么应该注意让类型实现IEquatable接口的Equals方法(通常实现的Equals方法除了调用自己的类型参数,还应该有一个重载函数调用object参数,以便在内部调用类型安全的Equals方法。这个定义接收object对象的重写函数么就是对IEquatable的Equals的实现),还有重写==和!=操作符方法。

考虑到排序,所以可能还需要实现IComparable的CompareTo方法,和IComparable的类型安全的CompareTo方法。实现了这些方法,那么<,<=,>,>=在内部调用类型安全的CompareTo方法也OK了。

(如果你觉得上面自己定义值类型的实现还有什么地方觉得遗漏,最好的方法其实就是去看int类型的定义就ok了)

5.4对象哈希码

FCL的设计者认为,如果能将任何对象的任何实例放到一个哈希表集合中,会带来很多好处。为此,System.Object提供了虚方法GetHashCode,它能获取任意对象的Int32哈希值。

如果你重写了Equals方法,那么还应重写GetHashCode方法。因为在System.Collection.Hashtable类型、System.Collections.Generic.Dictionary类型以及其他一些集合实现中,要求两个对象为了相等,必须具有相同的哈希码,所以重写了Equals,那么还应该重写GetHashCode,确保相等性算法和对象哈希码算法是一致的。

System.ValueType实现的GetHashCode采用了反射机制(它的速度较慢),并对类型的实例字段执行的XOR运算。建议自己实现GetHashCode,这样才能确切的掌握它所做的事,而且你的实现会比ValueType的实现快一些。

在自己实现哈希表集合时,或调用GetHashCode,千万不要对哈希码进行持久化,因为哈希码很容易改变。

小结

System.Object提供了虚方法GetHashCode,它能获取任意对象的Int32哈希码。

另外重写了Equals方法,那么最好重写GetHashCode方法。

因为在System.Collections.Hashtable类型和System.Collections.Generic.Dictionary类型以及其它的一些集合的实现中,要求两个对象必须要有相同的Hash码才被视为相等。

所以重写Equals方法实现相等性后,最好也重写GetHashCode方法,以确保相等型算法和对象哈希码算法一致。

另外重写时可以调用基类的GetHashCode方法,但是不要调用Object或者ValueType的GetHashCode方法,因为两者的实现性能不高。

包含相同值的两个不同对象应返回相同的哈希码。(作者建议不要对哈希码进行持久化,因为哈希码的算法可能会改变)

5.5dynamic基元类型

为了方便开发人员使用反射或者与基本组件通信,C#编译器允许将一个表达式的类型标记为dynamic.还可将一个表达式的结果放在一个变量中,并将变量的类型标记为dynamic,然后,可以用这个dynamic表达式/变量调用一个成员,比如字段、属性/索引器、方法、委托等。

代码使用dynamic表达式/变量调用一个成员时,编译器会生成特殊的IL代码来描述所需要的操作。这种特殊的代码称为payload(有效载荷)。在运行时,payload代码根据当前有dynamic表达式/变量引用的对象的实际类型来决定具体执行的操作。

不要混淆dynamic和var。用var声明的局部变量只是一种简化语法,它要求编译器根据一个表达式推断具体的数据类型。var关键字只能用于声明方法内部的局部变量,而dynamic关键字可用于局部变量,字段和参数。表达式不能转型为var,但可以转型为dynamic。必须实现初始化化var声明的变量,但无需初始化用dynamic声明的变量。

dynamic表达式其实与System.Object一样的类型。编译器假定你在表达式上进行任何操作都是合法的,所以不会生成任何警告和错误。但是试图在运行时执行无效操作,就会抛出异常。

不能定义对dynamic进行扩展的扩展方法,但可以定义对Object进行扩展的扩展方法。

不能将Lambda表达式或者匿名方法作为实参传给dynamic方法调用,因为编译器不能推断出要使用的类型。

C#内建的动态求值功能所产生的额外开销是不容忽视的。虽然能用动态功能简化语法,但也要看是否值得。

小结

dynamic基元类型是为了方便开发人员使用反射或者与其它非.net组件通信.

代码使用dynamic表达式/变量时,编译器生成特殊的IL代码来描述这种操作。这种特殊的代码被称为payload(有效载荷)。在运行时,payload根据dynamic表达式/变量引用的对象的实际类型来决定具体执行的操作。

dynamic类型在编译后实际上是作为System.object,然而它在元数据中被应用了System.Runtime.CompilerServices.DynamicAttribute的实例。局部变量除外,因为Attribute显然不能在方法内部使用。

另外使用的泛型的dynamic的代码时,泛型代码已经变异好了,将类型视为Object,编译器不在泛型代码中生成payload,所以也不会执行动态调度。

且编译器允许使用隐式转型语法,将表达式从dynamic转型为其它类型。

dynamic a=123;
Int32 b=a;

另外dynamic表达式的求值结果也是一个dynamic类型。

不能定义对dynamic进行扩展的扩展方法,不能将lambda表达式或匿名方法作为实参传给dynamic使用。

为COM对象生成可由“运行时”调用的包装时。Com组件的方法中使用任何Variant实际都转化为dynamic,这称为动态化。显著简化了与COM对象的操作。

当然用dynamic会有额外的性能开销,因为会引用一些必须的dll,然后执行一些动态绑定啊什么的。如果只是一两处用这个东西,还是用传统方法好一点。(一般会引用Microsoft.CSharp.dll,与com组件操作还会用到System.Dynamic.dll)

dynamic和var的区别是什么?

dynamic是在运行时检测实际类型,var是在编译时编译器已经能够确定实际类型。例如定义了一个var str = “abc”; 这完全等同于定义了string str = “abc”; 编译器可以判断出str只能是string类型,用str调用string类型的方法和属性时,智能提示可以显示所有string类型的方法和属性,但当定义dynamic str = “abc”; 时,编译器并不知道str的实际类型,只会在运行时做判断,即使我们用str调用一个根本不存在的方法,同样可以通过变异,但运行时会报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CLR via C# 第4 英文PDFKristin, words cannot express how /feel about our life together. cherish our family and all our adventures. I'm filled each day with love for Aidan (age 9)and Grant (age 5), you both have been an inspira- tion to me and have taught me to play and have fun Watching the two of you grow up has been so rewarding and enjoyable for me. am lucky to be able to partake in your lives. love and ap preciate you more than you could ever know Contents at a glance Introduction PART I CLR BASICS CHAPTER 1 The clr's execution model CHAPTER 2 Building, Packaging, Deploying, and Administering Applications and Types 33 chaPTeR 3 Shared Assemblies and Strongly Named Assemblies 65 PART I DESIGNING TYPES CHAPTER 4 Type Fundamentals 91 CHAPTER 5 Primitive, Reference, and Value Types 111 CHAPTER 6 Type and Member Basics 151 CHAPTER 7 Constants and fields 175 chaPTer 8 Methods 181 chaPTer 9 Parameters 209 CHAPTER 10 Properties 227 CHAPTER 11 Events 249 CHAPTER 12 Generics 265 CHAPTER 13 Interfaces 295 PARTⅢ ESSENTIAL TYPES CHAPTER 14 Chars, Strings, and Working with Text 317 CHAPTER 15 Enumerated Types and Bit Flags 361 CHAPTER 16 Arrays 373 CHAPTER 17 Delegates 391 CHAPTER 18 Custom Attributes 421 CHAPTER 19 Nullable value Types 441 PART IV CORE FACILITIES CHAPTER 20 Exceptions and state management 451 CHAPTER 21 The Managed Heap and Garbage Collection 505 CHAPTER 22 CLR Hosting and AppDomains 553 CHAPTER 23 Assembly Loading and reflection 583 CHAPTER 24 Runtime serialization 611 CHAPTER 25 Interoperating with WinRT Components 643 PAR V THREADING ChaPTEr 26 Thread basics 669 CHAPTER 27 Compute-Bound Asynchronous Operations 691 CHAPTER 28 IyO-Bound Asynchronous Operations 727 CHAPTER 29 Primitive thread Synchronization Constructs 757 CHAPTER 30 Hybrid Thread Synchronization Constructs 789 Index 823 Contents at a glance Contents Introduction XX PART CLR BASICS Chapter 1 The Clrs Execution Model 3 Compiling Source Code into Managed Modules Combining managed modules into assemblies Loading the Common Language Runtime 8 Executing Your Assembly's Code 11 IL and∨ erification 16 Unsafe Code The Native Code generator tool: ngen. exe 19 The Framework Class Library 22 The Common Type System The Common Language Specification Interoperability with Unmanaged Code 30 Chapter 2 Building, Packaging, Deploying, and Administering Applications and Types 33 NET Framework Deployment Goals 34 Building Types into a Module 35 Response Fil 36 A Brief Look at metadata 38 What do you think of this book We want to hear from you Microsoft is interested in hearing your feedback so we can continually improve our books and learning resources for you. To participate in a brief online survey, please visit microsoft. com/learning/booksurvey Combining Modules to Form an Assembly 45 Adding Assemblies to a Project by Using the Visual Studio IDE.51 Using the assembly Linker Adding Resource Files to an Assembly 53 Assembly Version Resource Information .54 Version numbers ..58 Culture Simple Application Deployment(Privately deployed Assemblies)...60 Simple Administrative Control(Configuration) 62 Chapter 3 Shared Assemblies and Strongly Named Assemblies 65 Two Kinds of Assemblies, Two Kinds of Deployment 66 Giving an Assembly a Strong Name 67 The global Assembly Cache 72 Building an Assembly That References a Strongly Named Assembly..74 Strongly named assemblies are tamper-Resistant 75 Delayed Signing Privately Deploying Strongly Named Assemblies How the Runtime Resolves Type References 80 Advanced Administrative Control( Configuration) 83 Publisher Policy control 86 PART I DESIGNING TYPES Chapter 4 Type Fundamentals 91 All Types Are Derived from System Object .91 Casting Between Types 93 Casting with the C# is and as Operators Namespaces and assemblies 97 How Things relate at Run time .101 Chapter 5 Primitive, Reference, and Value Types 111 Programming Language Primitive Types 111 Checked and Unchecked Primitive Type Operations 115 Reference Types and value Types 118 Boxing and Unboxing Value Types 124 Changing Fields in a Boxed Value Type by Using Interfaces and Why You Shouldnt Do This) 136 Object Equality and Identity 139 Object hash Codes .142 The dynamic Primitive Type ......144 Chapter 6 Type and member Basics 151 The Different Kinds of Type Members .151 Type visibilit 154 Friend assemblies 154 Member accessibility .156 Static Classes ...158 Partial Classes, Structures, and Interfaces .159 Components, Polymorphism, and Versioning 160 How the CLR Calls Virtual Methods, Properties, and Events 162 Using Type Visibility and Member Accessibility Intelligently...166 Dealing with Virtual Methods When Versioning Types 16 Chapter 7 Constants and Fields 175 Constants 175 Fⅰe|ds ...177 Chapter 8 Methods 181 Instance Constructors and Classes(Reference Types) 181 Instance Constructors and Structures(Value Types) 184 Type Constructors 187 Contents x Operator Overload Methods 191 Operators and Programming Language Interoperability 193 Conversion Operator Methods 195 Extension method 198 Rules and guidelines ....,200 Extending Various Types with Extension Methods 201 The Extension Attribute 203 Partial Methods 204 Rules and guidelines 207 Chapter 9 Parameters 209 Optional and Named Parameters 209 Rules and guidelines 210 The defaultParameter value and optional Attributes 212 Implicitly Typed Local Variabl 212 Passing parameters by reference to a Method 214 Passing a variable Number of arguments to a Method 220 Parameter and Return Type Guidelines 223 Const-nes 224 Chapter 10 Properties 227 Parameterless Properties 227 Automatically Implemented Properties 231 Defining Properties Intelligently 232 Object and collection Initializers 235 Anonymous Type .237 The System. Tuple type 240 Parameterful Properties 242 The performance of calling property accessor Methods 247 Property Accessor Accessibility 248 Generic prop A roperty Access 248
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值