Chapter 1 - Item 9 : Minimize Box and Unboxing

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

C# 自 object 繼承體系下主要有兩個分支:值型別與參考型別。值型別儲存在 stack,而參考型別則儲存在 Heap;在實務上有時需要自值型別轉換為參考型別(e.g. System.Object, interface.),這種操作稱之為裝箱(box)和拆箱(unbox)。以下用實際程式碼演示裝箱與拆箱是如何運作的,以及值型別在集合中的行為。

1. 拆箱與裝箱。

int firstNumber = 1;
int secondNumber = 2;
int thirdNumber = 3;

Debug.WriteLine( $"A few numbers : {firstNumber}, {secondNumber}, {thirdNumber}." );

{ } 內型別需為 System.Object,故會發生裝箱操作(int → object)。

object o = firstNumber; // box

接著呼叫 object.ToString 進行拆箱(object → int)。

int i = ( int )o; // unbox
string output = i.ToString( );

改寫程式使之不發生裝箱與拆箱:

Debug.WriteLine( $@"A few numbers : {firstNumber.ToString( )}, {secondNumber.ToString( )}, {thirdNumber.ToString( ).}" );

Boxing 示意圖:
    

2. 值型別在集合中的行為。    

// Using Person in a Collection.
var attendees = new List<Person>( );
Person p = new Person( ) { Name = "Old Name" };
attendees.Add( p ); // A copy of p stores in attendees.
            
// Try to change the name.
// Would work if Person was a reference type.
Person p2 = attendees [ 0 ]; // Gets a copy of Person from attendees [ 0 ]
p2.Name = "New Name"; // Modify p2s name, but won't change any value in attendees [ 0 ]
    
// Write "Old Name"
// Gets a copy of Person from attendees [ 0 ].
// Output the Name of Person which is "Old Name".
Debug.WriteLine( attendees [ 0 ].ToString( ) );
結論:
1. 盡量避免裝箱與拆箱,減少無謂的操作。

2. 裝箱發生的時機:
    a. 值型別轉換為 System.Object 或 interface 時。
    b. 值型別呼叫定義在 System.Object 中且非可覆寫的方法時(e.g. object.GetType( ) )。

參考資料:
How does Object.GetType() really work?
OpCodes.Constrained Field
• If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the 'this' pointer to the callvirt of method.
    
• If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of method by thisType.
    
• If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.

C# value type boxing under the hood