引用类型和值类型
应用类型:类
值类型:结构体和枚举
System.Object-->System.ValueType-->System.TimeSpan结构
System.Object-->System.ValueType-->System.Enum--> System.DayOfWeek枚举
值类型可以实现一个或多个接口,其本身是默认sealed.
类型声明为值类型时:具有基元类型的行为,是immutable类型;不需要继承,也不会派生;实例较小(<16字节),或者较大但不作为实参传递。
值类型具有未装箱与已装箱两种表示,定义自己的值类型时,应该重写Equals(两个对象字段完全匹配时返回true)和GetHashCode(考虑对象实例字段中的值,存在性能问题)。
CLR可为值类型添加mullability标识,该特性称为nullable类型。值类型实例在其内存(线程栈)被回收时,不会通过Finalize方法收到通知。
class SomeRef{
public Int32 x;
}
class SomeRef
{
[FieldOffset( 0)]
public Int32 x;
}
值类型的装箱和拆箱
对值类型装箱过程:在托管堆中分配内存(包括各个字段和两个额外成员);值类型的字段复制到新分配的堆内存;返回对象的地址。
泛型集合类允许开发人员在操作值类型的集合时不需要对集合中的项进行装箱/拆箱处理。
拆箱:CLR获取已装箱的对象中的各个字段的地址。 将这些字段包含的值从堆中复制到基于栈的值类型实例中。第二步只是复制,不是拆箱。拆箱不是装箱过程直接倒过来,拆箱的代价比装箱低得多。
由于未装箱的值类型没有同步索引块,所以不能使用System.Threading.Monitor类型的各种方法(或者使用C#的lock语句)让多个线程同步对这个实例的访问。
调用重写的虚方法(Equals, GetHashCode, ToString)的值类型实例不会被装箱。
调用非虚方法(GetType, MemberwiseClone),需要对值类型进行装箱。this实参是指向堆上一个对象的指针。
将值类型的未装箱实例转型为类型的某个接口时,需要对实例进行装箱。
对象的同一性和相等性:
同一性:identity; 相等性:equality.
由于一个类型能够重写Object的Equals方法,所以不能再调用这个Equals方法来测试同一性。为了修正这个问题,Object提供了一个静态方法ReferenceEquals来测试同一性,不能使用操作符==(除非操作数均为Object),因为其中某操作数可能重载了==。
重写Equals方法的同时:
1. 让类型实现System.IEquatable<T>接口的Equals方法
2. 重载==和!= 操作符方法。如果用于排序,还应实现System.IComparable的CompareTo方法和System.IComparable<T>的类型安全的CompareTo方法,同时考虑重载操作符(<, <=, >, >=),并在内部调用类型安全的CompareTo方法;
对象哈希码:
重写(定义)Equals之后,还要同时重写(定义)GetHashCode,是因为在一些集合的实现中,要求对像为了相等,必须具有相同的哈希码。
需要修改一个哈希表中的键对象时,正确的做法是移除原来的键值对,修改键对象,再将新的键值对放回到哈希表。
千万不要对哈希码进行持久化。例如密码持久化之后,升级到新版本CLR时,由于GetHashCode方法发生改变,导致原来用户不能登录。
dynamic基元类型:
{
static void Main( string[] args)
{
Object o1 = 123;
Int32 n1 = (Int32)o1; // OK Explicit convert
// Int32 n2 = o1; Error implicit convert
dynamic d1 = 123;
Int32 n3 = d1; // OK Implicit convert
Console.ReadLine();
}
}
var关键字仅能声明局部变量,必须显示初始化;无需初始化dynamic声明的变量。
不能对dynamic进行扩展的扩展方法,虽然可以定义对Object进行扩展的扩展方法;
不能将lambda表达式或者匿名方法作为实参传递给dynamic方法调用,因为编译器不能推断要使用的类型。
为COM对象生成wrapper程序集时,COM方法中使用的VARIANT会被转换成dynamic,这称为动态化(dynamicfication)。