例如下面的代码演示了我的思路:
class Program
{
static void Main(string[] args)
{
int i = 0;
IsNull(i); // Works fine
string s = null;
IsNull(s); // Blows up
}
static void IsNull<T>(T obj)
{
if (obj == null)
throw new NullReferenceException();
}
}
还有如下代码:
int i = 0;
bool b = i == null; // Always false
是否正在进行隐式对象转换?这样:
int i = 0;
bool b = (object)i == null;
最佳答案
xxbbcc 的回答假定 OP 询问“为什么 0 不等于 null”,这很可能就是问题所在。另一方面,在泛型类型的上下文中,关于装箱的问题通常与泛型类型通过避免装箱而提供的性能优势有关。
在考虑这个问题时,IL 可能会产生误导。它包括一个装箱指令,但这并不意味着值类型的装箱实例实际上将分配在堆上。 IL 将值“装箱”,因为 IL 代码也是通用的;将类型参数替换为类型参数是 JIT 编译器的责任。对于不可为 null 的值类型,JIT 编译器优化了用于装箱和检查结果的 IL 指令,因为它知道结果将始终为非 null。
我在示例代码中添加了一个 Thread.Sleep 调用,以便有时间附加调试器。 (如果您在 Visual Studio 中使用 F5 启动调试器,某些优化将被禁用,即使它是发布版本也是如此)。这是发布版本中的机器代码:
Thread.Sleep(20000);
00000000 55 push ebp
00000001 8B EC mov ebp,esp
00000003 83 EC 0C sub esp,0Ch
00000006 89 4D FC mov dword ptr [ebp-4],ecx
00000009 83 3D 04 0B 4E 00 00 cmp dword ptr ds:[004E0B04h],0
00000010 74 05 je 00000017
00000012 E8 AD 4B 6A 71 call 716A4BC4
00000017 33 D2 xor edx,edx
00000019 89 55 F4 mov dword ptr [ebp-0Ch],edx
0000001c 33 D2 xor edx,edx
0000001e 89 55 F8 mov dword ptr [ebp-8],edx
00000021 B9 C8 00 00 00 mov ecx,0C8h
00000026 E8 45 0E 63 70 call 70630E70
int i = 0;
0000002b 33 D2 xor edx,edx
0000002d 89 55 F8 mov dword ptr [ebp-8],edx
IsNull(i); // Works fine
00000030 8B 4D F8 mov ecx,dword ptr [ebp-8]
00000033 FF 15 E4 1B 4E 00 call dword ptr ds:[004E1BE4h]
string s = null;
00000039 33 D2 xor edx,edx
0000003b 89 55 F4 mov dword ptr [ebp-0Ch],edx
IsNull(s); // Blows up
0000003e 8B 4D F4 mov ecx,dword ptr [ebp-0Ch]
00000041 BA 50 1C 4E 00 mov edx,4E1C50h
00000046 FF 15 24 1C 4E 00 call dword ptr ds:[004E1C24h]
}
0000004c 90 nop
0000004d 8B E5 mov esp,ebp
0000004f 5D pop ebp
00000050 C3 ret
请注意,调用指令对 int 和 string 有不同的目标。他们在这里:
if (obj == null)
00000000 55 push ebp
00000001 8B EC mov ebp,esp
00000003 83 EC 0C sub esp,0Ch
00000006 33 C0 xor eax,eax
00000008 89 45 F8 mov dword ptr [ebp-8],eax
0000000b 89 45 F4 mov dword ptr [ebp-0Ch],eax
0000000e 89 4D FC mov dword ptr [ebp-4],ecx
00000011 83 3D 04 0B 32 00 00 cmp dword ptr ds:[00320B04h],0
00000018 74 05 je 0000001F
0000001a E8 ED 49 6E 71 call 716E4A0C
0000001f B9 70 C7 A4 70 mov ecx,70A4C770h
00000024 E8 2F FA E9 FF call FFE9FA58
00000029 89 45 F8 mov dword ptr [ebp-8],eax
0000002c 8B 45 F8 mov eax,dword ptr [ebp-8]
0000002f 8B 55 FC mov edx,dword ptr [ebp-4]
00000032 89 50 04 mov dword ptr [eax+4],edx
00000035 8B 45 F8 mov eax,dword ptr [ebp-8]
00000038 85 C0 test eax,eax
0000003a 75 1D jne 00000059
throw new NullReferenceException();
0000003c B9 98 33 A4 70 mov ecx,70A43398h
00000041 E8 12 FA E9 FF call FFE9FA58
00000046 89 45 F4 mov dword ptr [ebp-0Ch],eax
00000049 8B 4D F4 mov ecx,dword ptr [ebp-0Ch]
0000004c E8 DF 22 65 70 call 70652330
00000051 8B 4D F4 mov ecx,dword ptr [ebp-0Ch]
00000054 E8 BF 2A 57 71 call 71572B18
}
00000059 90 nop
0000005a 8B E5 mov esp,ebp
0000005c 5D pop ebp
0000005d C3 ret
和
if (obj == null)
00000000 55 push ebp
00000001 8B EC mov ebp,esp
00000003 83 EC 0C sub esp,0Ch
00000006 33 C0 xor eax,eax
00000008 89 45 F8 mov dword ptr [ebp-8],eax
0000000b 89 45 F4 mov dword ptr [ebp-0Ch],eax
0000000e 89 4D FC mov dword ptr [ebp-4],ecx
00000011 83 3D 04 0B 32 00 00 cmp dword ptr ds:[00320B04h],0
00000018 74 05 je 0000001F
0000001a E8 ED 49 6E 71 call 716E4A0C
0000001f B9 70 C7 A4 70 mov ecx,70A4C770h
00000024 E8 2F FA E9 FF call FFE9FA58
00000029 89 45 F8 mov dword ptr [ebp-8],eax
0000002c 8B 45 F8 mov eax,dword ptr [ebp-8]
0000002f 8B 55 FC mov edx,dword ptr [ebp-4]
00000032 89 50 04 mov dword ptr [eax+4],edx
00000035 8B 45 F8 mov eax,dword ptr [ebp-8]
00000038 85 C0 test eax,eax
0000003a 75 1D jne 00000059
throw new NullReferenceException();
0000003c B9 98 33 A4 70 mov ecx,70A43398h
00000041 E8 12 FA E9 FF call FFE9FA58
00000046 89 45 F4 mov dword ptr [ebp-0Ch],eax
00000049 8B 4D F4 mov ecx,dword ptr [ebp-0Ch]
0000004c E8 DF 22 65 70 call 70652330
00000051 8B 4D F4 mov ecx,dword ptr [ebp-0Ch]
00000054 E8 BF 2A 57 71 call 71572B18
}
00000059 90 nop
0000005a 8B E5 mov esp,ebp
0000005c 5D pop ebp
0000005d C3 ret
看起来差不多,对吧?但是,如果您先启动进程然后附加调试器,您会得到以下结果:
Thread.Sleep(20000);
00000000 55 push ebp
00000001 8B EC mov ebp,esp
00000003 50 push eax
00000004 B9 20 4E 00 00 mov ecx,4E20h
00000009 E8 6A 0C 67 71 call 71670C78
IsNull(s); // Blows up
0000000e B9 98 33 A4 70 mov ecx,70A43398h
00000013 E8 6C 20 F9 FF call FFF92084
00000018 89 45 FC mov dword ptr [ebp-4],eax
0000001b 8B C8 mov ecx,eax
0000001d E8 66 49 6C 70 call 706C4988
00000022 8B 4D FC mov ecx,dword ptr [ebp-4]
00000025 E8 46 51 5E 71 call 715E5170
0000002a CC int 3
优化器不仅删除了值类型的装箱,还通过完全删除它内联了对值类型的 IsNull 方法的调用。从上面的机器码看不出来,但是对引用类型的 IsNull 的调用也是内联的。 call 706C4988
指令似乎是 NullReferenceException 构造函数,而 call 715E5170
似乎是 throw
。
关于c# - 检查 null 时,泛型函数是否将值类型隐式转换为对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21343471/