可空值类型
Technically,这样的值类型依旧是值类型
值类型的方法和属性大部分不会因为值类型变量为null而发生
NullReferebceException ,因为这些方法基于模板类Nullable<T>实现,这种情况下会抛出InvalidOperationException 例外:调用GetType()方法时会抛出
NullReferebceException ,因为它不是virtual method,无法被Nullable<T> overwrite,保留了默认行为(抛出NullReferebceException )// IL Code public static partial class Nullable { public static int Compare<T>(System.Nullable<T> n1, System.Nullable<T> n2) where T : struct { throw null; } public static bool Equals<T>(System.Nullable<T> n1, System.Nullable<T> n2) where T : struct { throw null; } public static System.Type GetUnderlyingType(System.Type nullableType) { throw null; } } public partial struct Nullable<T> where T : struct { internal T value; private int _dummyPrimitive; public Nullable(T value) { throw null; } public bool HasValue { get { throw null; } } public T Value { get { throw null; } } public override bool Equals(object other) { throw null; } public override int GetHashCode() { throw null; } public T GetValueOrDefault() { throw null; } public T GetValueOrDefault(T defaultValue) { throw null; } public static explicit operator T (System.Nullable<T> value) { throw null; } public static implicit operator System.Nullable<T> (T value) { throw null; } public override string ToString() { throw null; } }
关于System.ValueType
重写了object的所有虚成员
GetHashCode(): 默认将调用转发给第一个非空字段
- 通过反射调用值类型的所有字段
- 对每个字段调用GetHashCode()方法
- 将所有字段的哈希值组合成一个最终的哈希值(组合方式可能会通过异或运算进行合并)
Equals(): 调用了大量反射,性能较差
Best Practice: 在频繁调用的情况(比如在集合或者字典里)重写这两个方法以获得更好的性能
装箱与拆箱
值类型的装箱( → object)会先在堆上分配一块空间,把值拷贝到堆上,然后将这个位置的引用返回
拆箱反之
查看CIL码可以看到box和unbox指令,注意控制这些操作的数量
常见的发生场景是,对于不接受泛型的集合类(比如ArrayList),其内部所有数据都会被转换为object,此时如果存储的是值类型则会发生boxing
Enum
System.ValueType → System.Enum → enum
给其中一个值赋值,后面的值都依次默认+1,前面的默认从0开始
Enum有一个基础类型,默认为int,可以是除char以外的任意整型
enum ConnectionState : short { Disconnected, Connecting = 10, Connected, // ... }
在传递enum参数时,不能传递一个显示的整型值(如1)进来,但是0可以,因为0可以隐式转换为任何枚举类型
若没有给枚举值显式赋值,则在插入枚举中部插入新的枚举值时,会导致后面的枚举值的显式值(即枚举值在内存中的实际值)发生改变,编译的时候全部重编
Best Practice: 为所有枚举值显式赋值
[Flags] 特性可以用于实现「位域操作」:[Flags] // 如果不显式写出各个枚举值的字面量,会默认变成二进制计算的 public enum FileAccess { Read = 1, // 0001 Write = 2, // 0010 Execute = 4, // 0100 Delete = 8// 1000 } static void Main() { FileAccess fileAccess = FileAccess.Read | FileAccess.Write; Console.WriteLine(fileAccess); // 输出: Read, Write bool canRead = (fileAccess & FileAccess.Read) == FileAccess.Read; Console.WriteLine(canRead); // 输出: True bool canExecute = (fileAccess & FileAccess.Execute) == FileAccess.Execute; Console.WriteLine(canExecute); // 输出: False }