public struct Point
{
private int m_x, m_y;
public Point(int x, int y)
{
m_x = x;
m_y = y;
}
public override string ToString()
{
return string.Format("{0},{1}", m_x, m_y);
}
}
上面是一個值類型的定義,下面創建一個實例,用在控制臺上輸出一些信息:
Point p = new Point(1, 1);
Console.WriteLine(p);
這與
Point p = new Point(1, 1);
Console.WriteLine(p.ToString());
這二者在輸出結果上完全一樣,也許很多人象我一樣,在平時工作中隨意使用,也不會去管它有什么不同?
但其實,Console.WriteLine(p)是會產生裝箱(box)指令的!
原因很簡單:Console.WriteLine的所有重載版本中,并沒有一個Console.WriteLine(Point p)的版本,所以默認會調用Console.WriteLine(Object o)這個版本,p會裝箱成Object,返回一個在堆上的引用。
而Console.WriteLine(p.ToString())則會調用Console.WriteLine(String s)這個重載版本,p.ToString()已經是一個String了,所以無需裝箱。
繼續來看一段稍微長一點的代碼:
using System;
namespace boxTest
{
class Program
{
static void Main(string[] args)
{
int i = 1;
test(5);
Console.WriteLine(i);//1
object obj = 1;
test(obj);
Console.WriteLine(obj);//1
string s = "1";
test(s);
Console.WriteLine(s);//"1"
P1 p1 = new P1(1);
test(p1);
Console.WriteLine(p1.X);//1
P2 p2 = new P2(1);
test(p2);
Console.WriteLine(p2.X);//5
Console.Read();
}
static void test(int i)
{
i = 5;
}
static void test(object o)
{
o = 5;
}
static void test(string s)
{
s = "5";
}
static void test(P1 p)
{
p.X = 5;
}
static void test(P2 p)
{
p.X = 5;
}
}
internal struct P1
{
private int _x;
public P1(int x)
{
_x = x;
}
public int X { set { _x = value; } get { return _x; } }
}
internal class P2
{
private int _x;
public P2(int x)
{
_x = x;
}
public int X { set { _x = value; } get { return _x; } }
}
}
上面代碼的5次輸出結果,您都猜對了嗎?
第
1
次輸出:因為i是值類型,參數傳遞默認是按值傳遞的,也就是說test方法體里的參數i是一個全新的副本,跟外界沒關系,方法調用完后,方法體內的i自動被清理,不影響方法體外的i
第
2
次輸出:雖然Object是引用類型,參數傳遞也是按引用傳遞的,但是方法體內o=5的賦值,使o指向了一個全新的"已裝箱的5",這時o與方法體外的obj已經是二個不同的對象了,有懷疑的同學,可用Object.ReferenceEquals方法輸出驗證,如下面這樣
static void test(object o)
{
object o1 = o;
Console.WriteLine(Object.ReferenceEquals(o1, o));//true
o = 5;
Console.WriteLine(Object.ReferenceEquals(o1, o));//false
}
但是在test(Object o)調用完成后,main方法后面還要繼續使用obj(因為有Console.WriteLine(obj)),所以obj此時也不會被列為垃圾回收的目標。test方法調用結束后,方法體內部的對象o,因不再使用將等候GC回收。
第
3
次輸出:String雖然也是引用類型,但是String的處理機制有別于其它引用類型(這個話題展開就可再寫一篇文章了,建議不清楚的同學去CLR VIR C#中的"字符、字符串和文本處理"相關內容),在test(String s)內對s賦值為新字符串時,同樣會生成一個新的對象,因此也不會影響到test方法體外的值。但是:跟第2次輸出不同的是,test(String s)調用結束后,字符串"5"卻不會被立即回收(即:字符串駐留機制),如果下次有人需要再次使用字符串"5",將直接返回這個對象的引用,這一點可通過觀察對象的HashCode看出端倪:
using System;
namespace boxTest
{
class Program
{
static void Main(string[] args)
{
string s = "1";
test(s);
string s1 = "1";
string s2 = "5";
Console.WriteLine("{0},{1},{2}", s.GetHashCode(), s1.GetHashCode(), s2.GetHashCode());
Console.Read();
}
static void test(string s)
{
Console.WriteLine("{0}", s.GetHashCode());
s = "5";
Console.WriteLine("{0}", s.GetHashCode());
}
}
}
輸出結果為:
-842352753
-842352757
-842352753,-842352753,-842352757
第
4
次輸出:struct類型的P1是值類型,類似第1次輸出中的解釋一樣,按值傳遞,方法體內修改的只是副本的值,也不會影響test體外的值.
第
5
次輸出:class類型的P2是引用類型,參數傳遞的其實是p2的地址(即指針),而且在test方法體內并未對p2重新賦值(指沒有類似p2 = new P2(1)類似的代碼),而只是修改了p2的屬性X,方法調用結束后,p2引用指向的地址沒有改變,但是這個地址中對應的值X已經變了,所以輸出5.
最后再來二個CLR VIR C#原書示例的簡化版
using System;
namespace boxTest
{
class Program
{
static void Main(string[] args)
{
P p1 = new P(1);
Console.WriteLine(p1);//1
p1.ChangeX(2);
Console.WriteLine(p1);//2
object o = p1;
((P)o).ChangeX(5);
Console.WriteLine(o);//這里將輸出2,而不是5 !
//解釋:((P)o).ChangeX(5);
//其實相當于 P p2 = (P)o; p2.ChangeX(5);
//所以根本沒改變p1中的_x值(因為P是值類型,p2與p1在內存中對應的是二個不同的地址,相互并不干擾),
//然后臨時生成的p2因為不再被使用,Main方法執行完成后,會自動清理
Console.Read();
}
}
struct P
{
private int _x;
public P(int i)
{
_x = i;
}
public void ChangeX(int x)
{
_x = x;
}
public override string ToString()
{
return string.Format("{0}", _x);
}
}
}
using System;
namespace boxTest
{
class Program
{
static void Main(string[] args)
{
P p1 = new P(1);
Console.WriteLine(p1);//1
p1.ChangeX(2);
Console.WriteLine(p1);//2
object o = p1;
((IChangeX)o).ChangeX(5);
Console.WriteLine(o);//這里將輸出5
//解釋: ((IChangeX)o).ChangeX(5); 相當于
//IChangeX _temp = (IChangeX)o;
//_temp.ChangeX(5);
//因為接口實際上返回的是引用(算是引用類型),
//所以這時_temp與o指向的是同一個內存地址,修改_temp就相當于修改o
Console.Read();
}
}
struct P :IChangeX
{
private int _x;
public P(int i)
{
_x = i;
}
public void ChangeX(int x)
{
_x = x;
}
public override string ToString()
{
return string.Format("{0}", _x);
}
}
interface IChangeX
{
void ChangeX(int x);
}
}
讓struct實現一個接口以后,情況就變了,同樣大家看注釋,不解釋。
?
要想寫出高性能的代碼,每個細節都要意識到背后發生的事情。所以象CLR VIR C#這類神作,沒事拿來翻翻,不斷加深印象還是很有必要的。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

