.NET对象的几种判等,耦合在一起,我尽量在任意一种判等里不涉及其他判等方式。但是Instace.Equals仍然不法避免不使用到Operator ==,所以我把Operator ==作为第三部分内容。
三、Operator==深入本质
默认情况下,Operator==在对引用类型对象判等的本质和Object.ReferenceEquals是相同的即确定指定的变量是否是相同的实例。
当然C#.NET可对Opeartor==进行定义,鄙人认为应该避免自定义Operator==。任意定义将会对使用者对Opeartor==的作用产生混淆。毕竟没有任何逻辑比a=a更有逻辑了。
对于自描述值类型来说如果想使用Operator==进行判等,那么就要对Opeartor==以及Opeartor!=分别进行定义。FCL的大部分值类型都是这样做的比如DateTime、Decimal等。
存在这样的情况Int32、Int64、Byte等值类型并未定义Opeartor==,但是编译器仍然支持以上类型变量直接使用Operator==进行判等。同查阅相关资料得知包括byte、shrot、 int、long、char、float、double、bool、decimal、string、object、dynamic共十二种类型为基元类型。编译器直接支持的数据类型称为基元类型。基元类型直接映射到Framework类库(FCL)中存在的类型。比如在C#中,int直接映射到System.Int32类型。
当两个int类型变量进行判等,首先将两个值压入计算堆栈,CLR通过CEQ指令直接对值进行判等。当然基元类型是支持定义Opeartor的比如Decimal就定义了Opeartor==。
有趣的是,sbyte、ushort、uint、ulong并非基元类型,也未定义Opeartor,但是以上几种类型直接使用Opeartor==判等,CSC编译器仍然能够编译通过。以ushort为例,请看一下代码:
1 static void Main() 2 { 3 ushort u1=1; 4 ushort u2 = 2; 5 var b = u1 == u2; 6 }
通过ILDASM反编译,得到的IL代码
1 .method private hidebysig static void Main() cil managed 2 { 3 .entrypoint 4 // 代码大小 11 (0xb) 5 .maxstack 2 6 .locals init (uint16 V_0, 7 uint16 V_1, 8 bool V_2) 9 IL_0000: nop 10 IL_0001: ldc.i4.1 //将整数值 1 作为 int32 推送到计算堆栈上。 12 IL_0002: stloc.0 //从计算堆栈的顶部弹出当前值并将其存储到索引 0处的局部变量列表中。 13 IL_0003: ldc.i4.2 //将整数值 2 作为 int32 推送到计算堆栈上。 15 IL_0004: stloc.1 //同上,存储到索引1处的局部变量列表 16 IL_0005: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上。 17 IL_0006: ldloc.1 //同上 18 IL_0007: ceq //如果这两个值相等,则将整数值 1 (int32) 推送到计算堆栈上;否则,将 0 (int32) 推送到计算堆栈上。 19 IL_0009: stloc.2 //同上 20 IL_000a: ret 21 } // end of method Program::Main
代码中添加了一部分注释,大概的意思就是将ushort隐身的看作int类型,在通过CEQ指令对基元类型进行判等,类似于以下代码:
1 static void Main() 2 { 3 int i1 = 1; 4 int i2 = 2; 5 var b = i1 == i2; 6 }
关于Instance.Equals将在下一篇文章中讲解。