裝箱與實值型別雖然很容易理解,但是在實際使用中,並不總是能100%用對

來源:互聯網
上載者:User
    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#這類神作,沒事拿來翻翻,不斷加深印象還是很有必要的。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.