溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

C#可空類(lèi)型原理是什么

發(fā)布時(shí)間:2021-12-21 10:36:45 來(lái)源:億速云 閱讀:133 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹“C#可空類(lèi)型原理是什么”,在日常操作中,相信很多人在C#可空類(lèi)型原理是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”C#可空類(lèi)型原理是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

為了方便演示,我就定義一個(gè)int?類(lèi)型,接收非null和null兩種情況。


       static void Main(string[] args)
       {
           int? num1 = 10;
           int? num2 = null;

           Console.WriteLine("執(zhí)行結(jié)束啦!");
           Console.ReadLine();
       }

1. 挖IL代碼

挖IL代碼簡(jiǎn)單,用ILSPY小工具就可以了,編譯后生成的IL代碼如下:


.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2048
// Code size 36 (0x24)
.maxstack 2
.entrypoint
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<int32> num1,
[1] valuetype [mscorlib]System.Nullable`1<int32> num2
)

IL_0000: nop
IL_0001: ldloca.s 0
IL_0003: ldc.i4.s 10
IL_0005: call instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
IL_000a: ldloca.s 1
IL_000c: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0012: ldstr "執(zhí)行結(jié)束啦!"
IL_0017: call void [mscorlib]System.Console::WriteLine(string)
IL_001c: nop
IL_001d: call string [mscorlib]System.Console::ReadLine()
IL_0022: pop
IL_0023: ret
} // end of method Program::Main

這IL代碼還是非常易懂的,比匯編簡(jiǎn)單多啦(┬_┬),可以看到int ? 就是 System.Nullable<int32> ,然后從valuetype 標(biāo)記可以看到這玩意是個(gè)值類(lèi)型,所以把上面的代碼回轉(zhuǎn)成C#代碼就是下面這樣。


   {
       static void Main(string[] args)
       {
           //int? num1 = 10;
           //int? num2 = null;

           Nullable<int> num3 = new Nullable<int>(10);
           Nullable<int> num4 = new Nullable<int>();

           Console.WriteLine("執(zhí)行結(jié)束啦!");
           Console.ReadLine();
       }

很簡(jiǎn)單吧,那怎么輸出num3和num4呢? 直接Console.WriteLine就好了。

C#可空類(lèi)型原理是什么

這里你肯定有一個(gè)疑問(wèn),為什么num3輸出10,而num4什么都沒(méi)輸出呢? 哈哈,這是因?yàn)镹ullable的ToString()被重寫(xiě)了,再來(lái)看下ToString被重寫(xiě)成啥樣了,代碼如下:

public struct Nullable<T> where T : struct
{
  private bool hasValue;

  internal T value;

  [NonVersionable]
  [__DynamicallyInvokable]
  public Nullable(T value)
  {
  this.value = value;
  hasValue = true;
  }

  [__DynamicallyInvokable]
  public override string ToString()
  {
  if (!hasValue)
  {
  return "";
  }
  return value.ToString();
  }
}

可以看到ToString方法里要么返回空字符串要么返回你在構(gòu)造函數(shù)中塞入的value,這這么簡(jiǎn)單,IL代碼挖到這里就可以了。

2. 挖機(jī)器代碼

要看num1和num2的機(jī)器代碼,其實(shí)也就是看 Nullable<T> 的內(nèi)存布局方式,這里我使用windbg,還是使用 !clrstack -l 查看線(xiàn)程棧。


    int? num1 = 10;
    int? num2 =null;

0:007> ~0s
ntdll!ZwReadFile+0x14:
00007ffc`ec11aa64 c3              ret
0:000> !clrstack -l
OS Thread Id: 0x5364 (0)
       Child SP               IP Call Site
ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 21]
   LOCALS:
       0x00000018a9dfeaf8 = 0x0000000a00000001
       0x00000018a9dfeaf0 = 0x0000000000000000

00000018a9dfed08 00007ffcd5b66c93 [GCFrame: 00000018a9dfed08]

從LOCALS中可以看到,num1和num2的線(xiàn)程棧上存放的內(nèi)容分別是0x0000000a00000001和 0x0000000000000000, 不過(guò)這值也挺奇怪的,一個(gè)是1一個(gè)是0。。。我們用 dd 命令把地址轉(zhuǎn)儲(chǔ)出來(lái)。


0:000> dd 0x00000018a9dfeaf8
00000018`a9dfeaf8  00000001 0000000a a9dfec08 00000018
0:000> dd 0x00000018a9dfeaf0
00000018`a9dfeaf0  00000000 00000000 00000001 0000000a

C#可空類(lèi)型原理是什么

在num1的內(nèi)存區(qū)域中有一個(gè)十六進(jìn)制值 0000000a ,這就是十進(jìn)制的10,那前面的 00000001 是什么東西呢? 大家不要忘啦, int?是語(yǔ)法糖, 你現(xiàn)在看的是 Nullable<T> 哈。。。

C#可空類(lèi)型原理是什么

看清楚啦,這個(gè)結(jié)構(gòu)體里面有兩個(gè)值類(lèi)型字段,自然 00000001 就是 hasValue=true啦。 num2也就好理解了,兩個(gè)默認(rèn)值也就是兩個(gè)0了。00000000 00000000。

三:有意外發(fā)現(xiàn)

1. int? 比 int 要占用更多的內(nèi)存

如果你的內(nèi)存數(shù)據(jù)量特別大的話(huà),你就要當(dāng)心了,int? 比 int 在x64上要多占4個(gè)字節(jié),也就是多一倍,無(wú)論線(xiàn)程棧還是托管堆。

2. 為什么bool要占用 4字節(jié)空間?

<1> 線(xiàn)程棧上的演示

肯定有人比較疑惑,bool在C#中不就是一個(gè)字節(jié)嘛? 你怎么說(shuō)是4個(gè)字節(jié)呢? 你要是問(wèn)我,我只能說(shuō)從windbg上看就是這樣的,x64系統(tǒng)的線(xiàn)程棧上就是以4個(gè)字節(jié)為一個(gè)單位,你不信的話(huà),我就在代碼中定義不同字段的 值類(lèi)型,你看看在線(xiàn)程棧上的分布不就好啦,以事實(shí)說(shuō)話(huà)。

           byte b1 = byte.MaxValue;
          byte b2 = byte.MaxValue;
          short b3 = short.MaxValue;
          short b4 = short.MaxValue;
          int b5 = int.MaxValue;
          int b6 = int.MaxValue;

0:000> !clrstack -l
OS Thread Id: 0xa98 (0)
ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 25]
  LOCALS:
      0x000000a8395fedbc = 0x00000000000000ff
      0x000000a8395fedb8 = 0x00000000000000ff
      0x000000a8395fedb4 = 0x0000000000007fff
      0x000000a8395fedb0 = 0x0000000000007fff
      0x000000a8395fedac = 0x000000007fffffff
      0x000000a8395feda8 = 0x000000007fffffff

然后把最小的地址0x000000a8395feda8 轉(zhuǎn)儲(chǔ)出來(lái)。


0:000> dd 0x000000a8395feda8
000000a8`395feda8  7fffffff 7fffffff 00007fff 00007fff
000000a8`395fedb8  000000ff 000000ff 395feec8 000000a8
000000a8`395fedc8  395fefc8 000000a8 395fee00 000000a8
000000a8`395fedd8  d5b66c93 00007ffc 98e72d30 000001ee
000000a8`395fede8  76504140 00007ffc 00000000 00000000
000000a8`395fedf8  00000000 00007ffc 395feef0 000000a8
000000a8`395fee08  971d0b20 000001ee 00000000 00000000
000000a8`395fee18  d5b66b79 00007ffc 00000000 00000000

對(duì)比一下可以看到上面的 7fffffff, 00007fff,000000ff就是相應(yīng)的int,short,byte的MaxValue, 都是占用4個(gè)字節(jié)的空間,沒(méi)問(wèn)題吧。

<2> 托管堆演示


   var arr1 = new int[] { 10 };
   var arr2 = new int?[] { 14 };

0:000> !clrstack -l
OS Thread Id: 0x23f8 (0)
000000859a1fec60 00007ffc76630967 ConsoleApp4.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp4\Program.cs @ 32]
   LOCALS:
       0x000000859a1feca0 = 0x000002773cb32d70
       0x000000859a1fec98 = 0x000002773cb32d90

000000859a1feeb8 00007ffcd5b66c93 [GCFrame: 000000859a1feeb8]
0:000> !do 0x000002773cb32d70
Name:        System.Int32[]
MethodTable: 00007ffcd2d58538
EEClass:     00007ffcd2ec5918
Size:        28(0x1c) bytes
Array:       Rank 1, Number of elements 1, Type Int32 (Print Array)
Fields:
None
0:000> !do 0x000002773cb32d90
Name:        System.Nullable`1[[System.Int32, mscorlib]][]
MethodTable: 00007ffcd3fb2058
EEClass:     00007ffcd30221a0
Size:        32(0x20) bytes
Array:       Rank 1, Number of elements 1, Type VALUETYPE (Print Array)
Fields:
None

0:000> !objsize 0x000002773cb32d70
sizeof(000002773cb32d70) = 32 (0x20) bytes (System.Int32[])
0:000> !objsize 0x000002773cb32d90
sizeof(000002773cb32d90) = 32 (0x20) bytes (System.Nullable`1[[System.Int32, mscorlib]][])

可以看到,一個(gè)是28byte,一個(gè)是32byte,多出來(lái)的就是那個(gè)hasValue哈,有一點(diǎn)要注意了,用!objsize打出來(lái)都是32byte,這是因?yàn)?8byte要靠8對(duì)齊就變成32byte啦, 然后我把兩個(gè)值類(lèi)型轉(zhuǎn)儲(chǔ)出來(lái),如下圖:

C#可空類(lèi)型原理是什么

到此,關(guān)于“C#可空類(lèi)型原理是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI