🚫 Ad Blocker Detected

Please disable your AD blocker to continue using this site. Ads help us keep the content free! please press keyboard F5 to refresh page after disabled AD blocker

請關閉廣告攔截器以繼續使用本網站。廣告有助於我們保證內容免費。謝謝! 關閉後請按 F5 刷新頁面

0%

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 許可協議。轉載請註明出處!

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