真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

c#基礎(chǔ)系列之值類型和引用類型的深入理解

前言

創(chuàng)新互聯(lián)公司主要從事做網(wǎng)站、網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)托里,10年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18980820575

不知不覺已經(jīng)踏入坑已10余年之多,對于c#多多少少有一點自己的認(rèn)識,寫出來渴求同類抨擊,對自己也算是個十年之癢的一個總結(jié)。

C#把數(shù)據(jù)類型分為值類型和引用類型

1.1:從概念上來看,其區(qū)別是值類型直接存儲值,而引用類型存儲對值的引用。

1.2:這兩種類型在內(nèi)存的不同地方,值類型存儲在堆棧中,而引用類型存儲在托管對上。存儲位置的不同會有不同的影響。

下面話不多說了,來一起看看詳細(xì)的介紹吧

基本概念

CLR支持兩種類型:值類型和引用類型。 面試過很多5年左右的同學(xué),有很多連值類型和引用類型的基本概念都回答不上來,難道現(xiàn)在的c#開發(fā)人員基礎(chǔ)這么弱了嗎?還是大家都不重視基礎(chǔ)呢?這個隨便找一篇博客都可以基礎(chǔ)入門的。

c#基礎(chǔ)系列之值類型和引用類型的深入理解

引用類型

哪些類型是引用類型呢?其實一個可以稱為”類“的類型都是引用類型。 引用類型總是從托管堆上分配的,常用的語法就是New XX(). C#的new 操作符會返回對象的指針 - 也就是指向?qū)ο髷?shù)據(jù)的內(nèi)存地址的一個引用。引用類型的傳遞其實傳遞的是對象的指針(string類型比較特殊),所以在特定的場景下性能是高于值類型的。一個引用類型在創(chuàng)建時默認(rèn)為null,也就是說當(dāng)前變量不指向一個有效的對象,也就是我們常遇到的異常“未將對象引用設(shè)置到對象的實例”。

值類型

因為引用類型變量都需要進(jìn)行一次堆內(nèi)存的分配,這會給GC造成很大的壓力,所以CLR提供了輕量級類型“值類型”。 值類型一般在線程棧上分配。(注意:值類型可以嵌入一個引用對象中)一個值類型變量其實就包含了值類型實例的值,所以它沒有引用類型的指針(大家猜想值類型需不需要類型對象指針呢?)

相同點和不同點

相同點

  • 值類型和引用類型都是System.Object的子類
  • 值類型和引用類型都可以繼承接口。(很多人都認(rèn)為值類型不能繼承接口)
 interface Itest
 {
 void test();
 }
 struct TestStruct : Itest
 {
 public void test()
 {
 throw new NotImplementedException();
 }
 }

不同點

  • 值類型分配在堆棧上,引用類型是在托管堆上分配的。這里需要指出一點:如果一個引用類型中的某個屬性是值類型,這個值類型的屬性是分配在托管堆上的。
  • 所有的值類型都是隱式密封的(sealed),例如 :你不可能繼承int 來構(gòu)造自己的類型。
  • 值類型的每一次賦值都會執(zhí)行一次逐字段的復(fù)制,所以如果是頻繁賦值也會造成性能上的壓力,引用類型的賦值只是指針的傳遞,其實也是生成新的指針實例。
  • 引用類型額外有類型對象指針和同步塊索引,值類型是沒有的。所以我們平時使用lock 鎖的對象不可能是值類型,因為值類型沒有同步塊索引

c#基礎(chǔ)系列之值類型和引用類型的深入理解

性能

有的同學(xué)說值類型的性能高于引用類型,那為什么不都用值類型呢?引用類型也是如此。任何東西都有兩面性,只有合適的類型,沒有萬能的類型。

1、值類型:所謂的.net Framework中的“輕量類型”,為什么說是“輕量”呢,這和他的內(nèi)存分配有直接關(guān)系,因為值類型是分配在棧上,所以在GC的控制之外,不會對GC造成壓力。那是不是可以隨便用呢?當(dāng)然不是,舉個例子:我自定義一個struct 類型作為一個方法的參數(shù)會發(fā)生什么呢?每次調(diào)用都會發(fā)生全字段的賦值,這是不可接受的,這也是典型的值類型勿用場景。

2、引用類型:引用類型分配在堆中,所以會影響GC,如果頻繁的初始化引用類型,對GC的壓力是很大的,因為每一次分配都有可能會強(qiáng)制執(zhí)行一次垃圾收集操作。另外提一點,引用類型的所占內(nèi)存,并非所有屬性/字段的和,堆上分配的每個對象都有一些額外的成員,這些成員必須初始化。(類型對象指針和內(nèi)存塊索引)。

3、裝箱拆箱:所謂裝箱就是將值類型轉(zhuǎn)化為引用類型的過程。拆箱則相反(只是概念上相反,實際編譯器的操作不一樣)。有的同學(xué)說裝箱拆箱影響性能,那到底是裝箱影響呢還是拆箱呢還是都影響呢?

裝箱發(fā)生了什么過程呢:

  • 在托管堆中分配好內(nèi)存,分配的內(nèi)存量是值類型的各個字段需要的內(nèi)存量加上托管堆上所以對象的兩個額外成員(類型對象指針,同步塊索引)需要的內(nèi)存量
  • 值類型的字段復(fù)制到新分配的堆內(nèi)存中
  • 返回對象的地址,這個地址就是這個對象的引用

拆箱發(fā)生了什么過程呢:

  • 獲取已經(jīng)裝箱的值類型實例的指針
  • 把獲取到的值復(fù)制到棧

所以裝箱是比較耗費性能的,還有可能引發(fā)一次GC操作,而拆箱只是一個獲取指針的過程耗費資源要比裝箱小的多。注意:一個對象拆箱之后只能還原為原先未裝箱之前的類型,例如:你不能把int32類型裝箱后還原為int16類型。 所以面試的時候可以和面試官裝B一下了~~

c#基礎(chǔ)系列之值類型和引用類型的深入理解

測試?yán)?/strong>

值類型引用類型分別初始化N次消耗的時間,代碼如下

static void Main(string[] args)
 {
 Console.WriteLine("test start");
 int totalCount = 10000000;
 Stopwatch sw = new Stopwatch();
 sw.Start();
 for (int i = 0; i < totalCount; i++)
 {
  TestRef temp = new TestRef() { Id = i, Name = "test" };
 }
 sw.Stop();
 Console.WriteLine($"引用類型耗時:{sw.ElapsedMilliseconds}");
 sw.Reset();
 sw.Start();

 for (int i = 0; i < totalCount; i++)
 {
  TestVal temp = new TestVal() { Id = i, Name = "test" };
 }
 sw.Stop();
 Console.WriteLine($"值類型耗時:{sw.ElapsedMilliseconds}");
 Console.Read();
 }

 class TestRef
 {
 public int Id { get; set; }
 public string Name { get; set; }
 }
 struct TestVal
 {
 public int Id { get; set; }
 public string Name { get; set; }
 }

運(yùn)行結(jié)果:

引用類型耗時:205
值類型耗時:152

可見初始化速度值類型是優(yōu)于引用類型的,也可能是引用類型引發(fā)了GC導(dǎo)致。

作為方法參數(shù)傳遞,代碼如下:

static void Main(string[] args)
 {
 Console.WriteLine("test start");
 long totalCount = 1000000000;
 Stopwatch sw = new Stopwatch();
 sw.Start();

 TestRef tempRef = new TestRef() { Id = 1, Name = "test" , Name2="r3rewfdsafdsa", Name3="fsrewfdsafdsafdsa", Name4="fdafdasfdsafdsa", Name5="432tretsfds", Name6="fdsafdasfdasfd" };
 for (int i = 0; i < totalCount; i++)
 {
  TestR(tempRef);
 }
 sw.Stop();
 Console.WriteLine($"引用類型耗時:{sw.ElapsedMilliseconds}");
 sw.Reset();
 sw.Start();
 TestVal tempVal = new TestVal() { Id = 1, Name = "test", Name2 = "r3rewfdsafdsa", Name3 = "fsrewfdsafdsafdsa", Name4 = "fdafdasfdsafdsa", Name5 = "432tretsfds", Name6 = "fdsafdasfdasfd" };
 for (int i = 0; i < totalCount; i++)
 {
  TestV(tempVal);
 }
 sw.Stop();
 Console.WriteLine($"值類型耗時:{sw.ElapsedMilliseconds}");
 Console.Read();
 }
 static void TestR(TestRef r)
 {
 return;
 }
 static void TestV(TestVal v)
 {
 return;
 }

 class TestRef
 {
 public int Id { get; set; }
 public string Name { get; set; }
 public string Name2 { get; set; }
 public string Name3 { get; set; }
 public string Name4 { get; set; }
 public string Name5 { get; set; }
 public string Name6 { get; set; }
 }
 struct TestVal
 {
 public int Id { get; set; }
 public string Name { get; set; }
 public string Name2 { get; set; }
 public string Name3 { get; set; }
 public string Name4 { get; set; }
 public string Name5 { get; set; }
 public string Name6 { get; set; }
 }

運(yùn)行結(jié)果:

引用類型耗時:4437
值類型耗時:5226

可見在普通情況下,作為參數(shù)值類型和引用類型用時差距不大,但是,如果值類型的實例屬性比較多的情況下差距降進(jìn)一步拉大。

非正式環(huán)境測試用例,結(jié)果僅供參考

應(yīng)用場景

不止是面試的時候經(jīng)常問應(yīng)用場景這個問題,就是自己平時寫程序也應(yīng)該清楚。程序設(shè)計選擇的時候大部分場景都是用引用類型,但是如果你滿足下列條件,值類型可能更適用:

  • 類型不會派生出任何其它類型,也就是說不會有被繼承的可能
  • 類型不需要繼承其他類型
  • 類型的實例比較小,并且不會被作為方法參數(shù),不會被頻繁賦值
  • 你永遠(yuǎn)不會用到類型釋放時候的通知,因為引用類型利用析構(gòu)函數(shù)可以利用其他手段可以得到釋放時候的通知。
  • 如果你的類型實例不會發(fā)生值的改變或者可以認(rèn)為是readonly性質(zhì)的,值類型或許是首選。

其他

  • 所有的值類型都從System.ValueType 派生,System.ValueType繼承System.Object,但是System.ValueType 重寫了Equals 和GetHashCode 方法,其實在這里才是真正和引用類型的分割線。
  • 因為值類型有裝箱拆箱的操作,所以像ArrayList這樣的集合性能是非常令人擔(dān)憂的。所以c# 2.0 出現(xiàn)了泛型 例如:List .....來保證了類型安全,同時又避免了拆箱裝箱,因為不是我定義的類型 ,你TMD根本連編譯器那一關(guān)都過不了 哈哈哈~~~~

順便說一句,好久不寫博客,樣式真實花時間啊,后來干脆寫markdown格式的,請大家見諒!!

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對創(chuàng)新互聯(lián)的支持。


新聞名稱:c#基礎(chǔ)系列之值類型和引用類型的深入理解
文章分享:http://weahome.cn/article/jpgdos.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部