c# - 检查 null 时,泛型函数是否将值类型隐式转换为对象?

标签 c# reference null value-type

例如下面的代码演示了我的思路:

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/

相关文章:

c# - 如何获取字符串中的向量位置并将其存储为整数?

Javascript 正则表达式匹配出现空值

php - 如何在 MySQL 数据库表中的字段上添加 'not empty' 约束?

java - 使用特定对象引用创建具有随机数的数组

php - 为什么 php api 在用 bind_result 重写代码后返回 null?

c# - Appveyor nuget分析器

c# - CSharpCodeProvider "string"不包含 "Select"的定义。 CSCC 遇到 System.Linq 问题

inheritance - 继承类是否应该在基类的同一个cs文件中?

c# - Xamarin.Android nuget 包引用 System.Web

java - 使引用不可变?