c#中值類型和引用類型的基礎(chǔ)教程
前言
值類型和引用類型,是c#比較基礎(chǔ),也必須掌握的知識點(diǎn),但是也不是那么輕易就能掌握,今天跟著老胡一起來看看吧。
典型類型
首先我們看看這兩種不同的類型有哪些比較典型的代表。
典型值類型
int, long, float, double等原始類型中表示數(shù)字的類型都是值類型,表示時間的datatime也是值類型,除此之外我們還可以通過關(guān)鍵字struct自定義值類型。
典型引用類型
原始類型中,array, list, dictionary, queue, stack和string都是引用類型,除此之外我們通過關(guān)鍵字class自定義引用類型。
基類
c#中所有的類型都最終繼承自O(shè)bject,這是沒有疑問的,但是這其中還有些微區(qū)別。
值類型基類
對于值類型來說,除了最終繼承自O(shè)bject,還繼承自ValueType,繼承鏈如下
但是請不要誤解,這里僅僅指的是值類型天然是ValueType,但是不代表值類型能夠這么聲明
struct Struct1 : ValueType { }
這樣是會引起編譯錯誤的,值類型不能繼承任何其他類型,值類型只能實(shí)現(xiàn)接口,不能繼承自其它類型。只有引用類型既可以實(shí)現(xiàn)接口也能繼承自其它類型。順便說一下,還有一點(diǎn)比較重要的是,ValueType重寫了Object基類的Equals方法和GetHashCode方法,所以當(dāng)使用Equals比較兩個值類型的時候,系統(tǒng)會比較兩個值類型的各個屬性是否相等,再返回結(jié)果,這就是所謂的相等性。與此相對,引用類型在使用Equals的時候,會在后臺調(diào)用object.ReferenceEquals,換言之,引用類型在比較相等性的時候會考慮同一性。
引用類型基類
對于引用類型就沒有那么麻煩,引用類型不會繼承自ValueType。引用類型可以繼承其他類型。
在內(nèi)存中的表現(xiàn)
我們都知道,C#將內(nèi)存分為了兩部分,一個是Stack,另外一個是Managed Heap。一般來說,用于函數(shù)調(diào)用進(jìn)棧,函數(shù)返回出棧,用的是Stack,而當(dāng)創(chuàng)造一個新的實(shí)例時,會根據(jù)創(chuàng)建的實(shí)例屬于值類型還是引用類型決定使用Stack還是Managed Heap。
值類型在內(nèi)存中
當(dāng)創(chuàng)建一個值類型對象時,c#會在Stack上面創(chuàng)建一塊空間,這塊空間就存放這個值類型對象。
int是一個典型的值類型,如下語句
int age = 10;
會存在于內(nèi)存中的Stack上面。
如果把值類型的實(shí)例賦值給另外一個值類型,那么效果就是復(fù)制一個新的值類型實(shí)例。
int myAge = age;
引用類型在內(nèi)存中
與值類型在內(nèi)存中的表現(xiàn)不一樣,創(chuàng)建一個引用類型的實(shí)例,不但會在Stack上面新建一個引用,還會在Heap上面劃分出內(nèi)存以容納該引用類型實(shí)例。用戶在使用的時候通過Stack上面的變量間接引用該實(shí)例。
class Author { public string Name{get;set;} public int Age{get;set;} } Author author = new Author(){Name="deatharthas", Age= 32};
注意看和值類型在內(nèi)存中的區(qū)別,引用類型通過Stack上的變量訪問位于Heap上面的實(shí)例。
在賦值的時候,拷貝的僅僅是Stack上面的變量,新拷貝出來的對象和舊的對象指向的是同一塊內(nèi)存。
Author myAuthor = author;
這個時候,author和myAuthor指向同一塊內(nèi)存,稱為同一性,通過調(diào)用
object.ReferenceEquals(myAuthor, author);
可以得到驗(yàn)證。
但可能有細(xì)心的朋友會有疑問了,不是說int是值類型,值類型是存在于Stack上面的嗎?為什么在author類里面,它會在Heap里面呢?贊一個細(xì)心!值類型一般存在于Stack上面,但如果某個值類型包含于引用類型,那么它也會隨著那個引用類型存放在Heap上面。
當(dāng)參數(shù)時的行為區(qū)別
c#中的參數(shù)傳遞默認(rèn)都是傳值(by value),但是根據(jù)所傳遞對象是值類型還是引用類型,它們的行為還是有所區(qū)別,現(xiàn)在我們來看看。
值類型當(dāng)參數(shù)
值類型當(dāng)參數(shù)的時候,傳遞到函數(shù)內(nèi)部的是一份值類型的拷貝,所以在函數(shù)內(nèi)部修改這個拷貝不會影響原對象。除非我們在傳遞參數(shù)的時候使用了ref或者out。
引用類型當(dāng)參數(shù)
如果參數(shù)是引用類型,傳遞到函數(shù)內(nèi)部的依然是一份拷貝,但是這個拷貝是其在Stack上面的變量的拷貝,就像上面的賦值那個例子。所以這個時候這份拷貝其實(shí)和原對象指向同一塊內(nèi)存(指向同一性),修改這個對象可以反映到原對象上面。
謹(jǐn)慎返回引用類型
編程是一項需要謹(jǐn)慎的工作,有時候我們經(jīng)常會犯一些錯誤,而這些錯誤又是那么的不明顯以至于不摔坑幾次,我們根本察覺不了,考慮下面一個例子。
class People { public string Name { get; set; } public int Age { get; set; } private People _Father = null; public People Father { get { return _Father; } } public People(People father) { _Father = father; } public void ShowFather() { Console.WriteLine("father's name is " + Father.Name + " and his age is " + Father.Age); } } class Program { static void Main(string[] args) { People father = new People(null) { Name = "father", Age = 60 }; People son = new People(father); son.ShowFather(); Console.ReadLine(); } }
看起來沒什么問題,對吧?Father沒有提供setter,似乎是安全的。但是我們試試下面的代碼。
static void Main(string[] args) { People father = new People(null) { Name = "father", Age = 60 }; People son = new People(father); var f = son.Father; f.Name="Changed"; son.ShowFather(); Console.ReadLine(); }
看,發(fā)現(xiàn)了什么,外部改變了本來應(yīng)該被封裝所保護(hù)的Father屬性,封裝被破壞了!
稍微一想我們應(yīng)該能明白這個道理,F(xiàn)ather屬性返回的拷貝的變量和原Father變量指向同一塊實(shí)例。要想解決這個問題,我們要么返回一個值類型,要么返回一個全新的對象。修改Father屬性如下:
public People Father { get { return new People(_Father._Father) { Name = _Father.Name, Age = _Father.Age }; } }
再次測試,
這次封裝就沒問題了。
總結(jié)
我們大概知道了值類型和引用類型的區(qū)別,包括它們的行為,在內(nèi)存的居住方式,以及使用引用類型時可能會遇到的暗坑,希望大家通過閱讀這篇文章,能夠加深一些對它們的了解,少走一些彎路。
今天也簡單的提到了比較時的同一性,和預(yù)防封裝被破壞所采用的返回一個新的實(shí)例拷貝的策略(這個時候適合使用DeepCopy),我們之后有機(jī)會再詳細(xì)聊。
到此這篇關(guān)于c#中值類型和引用類型的文章就介紹到這了,更多相關(guān)c#值類型和引用類型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Unity Line Renderer組件的常用屬性說明
這篇文章主要介紹了基于Unity Line Renderer組件的常用屬性說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04C#中利用Lotus notes公共郵箱發(fā)送郵件的方法
這篇文章主要給大家介紹了關(guān)于C#中利用Lotus notes公共郵箱發(fā)送郵件的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2018-02-02C#利用itext實(shí)現(xiàn)PDF頁面處理與切分
這篇文章主要介紹了如何在C#中使用itext做一個pdf的頁面大小一致性處理,然后再根據(jù)數(shù)據(jù)切分出需要的pdf,感興趣的小伙伴可以了解一下2022-04-04