C# Boxing vs UnBoxing

前言:

BoxingUnBoxing在.net中,我們可能在無意識使用到但這個事情確會造成一些效能影響…

.NET兩種類型

在.NET有分兩種類型

  1. 值類型(int,double,char….)
  2. 參考類型(自行宣告的類別,string….)

而存放資料的方式也有兩種:

  1. 堆疊Stack
  2. 堆積Heap

談談Boxing和UnBoxing之前,我們先來了解StackHeap

值類型(Value Type)會存取在Stack記憶體區塊中

參考類型(Reference Type)內容會在Heap記憶體區塊上,Stack會指向Heap上記憶體位置(有點像c++傳址)

如下圖

了解StackHeap

我們來談談BoxingUnBoxing

Boxing:

型態由大轉小

1
2
int i=20;
object o=(object)i;
int強制轉型為object 因為我們所有物件都是繼承於object物件

原本值類型存在Stack中,但因為我們強轉成Object = 20會存在Heap記憶體區塊中.

因為Object是ReferType型別,這個現象就是Boxing

如下圖

UnBoxing:

型態由小轉大(小轉大會有轉型出錯的問題)

1
2
3
int i=20;
object o=(object)i;
int j=(int)o;

Object強轉成int在這個案例不會有問題,但如果是將o轉為char就會有問題
在執行UnBoxing如下圖

可以看到原本存在Heap上值 我們會把他搬回Stack並附值給J

Heap上直搬回Stake上就會遇到UnBoxing.

.Net現實生活中常遇到的案例

  • String.Format
  • DataTable

String.Format的Boxing

1
public static string Format(string format, params object[] args)

我們常使用上面String.Format重載方法,但使用這個方法會不小心遇到Boxing問題

我們在呼叫方法時假如參數是一個Value Type,.Net會在呼叫前把此值複製在傳入方法中(如果是Refer Type傳入此物件Heap記憶體位置).

String.Format吃參數是Object,所以如果傳入參數是Value Type如(1,1.1m)就會遇到Boxing.

但如果我們在呼叫String.Format前使用ToString方法就可以避免Boxing的動作,$"{times.ToString()}".

DataTable的Boxing UnBoxing

我們在ADO.Net將資料存放在DataTable就會經歷一次Boxing在利用DataTable.Row[][]返回是一個Object型態資料(因為會把ValueType型別資料放進Heap中).

我們在取用時會把Object轉成我們希望型態(UnBoxing).

1
2
DataTable dt= new DataTable();
dt.Rows[0]["col1"] //返回一個object型態的物件

所以我在讀取DB資料時建議使用DataReader而不是使用DataTable,因為使用DataReader可以直接去得使用型態(避免Boxing and UnBoxing).

常使用誤區string.format Boxing UnBoxing

在開始說明之前先問問大家兩個問題

下面兩段程式碼是否是一樣?

如果不一樣是哪裡不一樣?

$""string.format語法糖.

1
2
3
4
5
int intVal = 1;
int intVal1 = 2;
int intVal2 = 2;
int intVal3 = 3;
$"{intVal.ToString()} {intVal2.ToString()} {intVal3.ToString()}";
1
2
3
4
5
int intVal = 1;
int intVal1 = 2;
int intVal2 = 2;
int intVal3 = 3;
$"{intVal} {intVal2} {intVal3}";

上面答案非常明顯是不一樣,但不一樣在哪裡呢?

Boxing和UnBoxing.

要了解String.Format Boxing和UnBoxing之前我們要先了解function是如何傳參數的.

Function如何傳參數

在.net我們常常在寫function但你有注意參數是如何被傳的嗎?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void Main(string[] args)
{
var a = new A()
{
Age = 100
};

FunctionA(a);
Console.WriteLine($"main a.Age {a.Age}");

var i = 100;
FunctionInt(i);
Console.WriteLine($"main i {i}");

Console.Read();
}

public static void FunctionA(A a)
{
Console.WriteLine($"FunctionA {a.Age}");
a.Age = 0;
}

public static void FunctionInt(int a)
{
Console.WriteLine($"FunctionInt {a}");
a = 0;
}

執行結果如下圖

alt

那是因為.net在傳參數時

  • 如果方法參數是Ref Type會copy address當作參數進去
  • 如果方法參數是Value Type會copy value當作參數進去

我們看String.Format其中一個重載方法,是傳入object[]當作參數.

1
public static string Format(string format, params object[] args) => args != null ? string.FormatHelper((IFormatProvider) null, format, new ParamsArray(args)) : throw new ArgumentNullException(format == null ? nameof (format) : nameof (args));

因為單純傳入value type會導致參數需要boxing(因為方法參數吃object)

所以value type使用ToString方法傳入String.Format方法,先把value type轉成refer type的string就不會造成boxing unboxing效能問題了.

Box_UnBoxing Sample Code

小結

希望本篇文章可以讓大家對於Boxing和UnBoxing更了解,避免踏入這個問題中。

參考連結

參考 MSDN https://msdn.microsoft.com/zh-tw/library/yz2be5wk.aspx

此文作者:Daniel Shih(石頭)
此文地址https://isdaniel.github.io/boxing-unboxing/
版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!


如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^