.Net常識 值類型和引用類型


值類型和引用類型是.net里面的一個基本概念

在面試的時候也經常遇到

關於這個概念有很多誤解,經常聽到下面的說法

1.兩者的區別是值類型分配在堆棧上,引用類型分配在堆上

  這句話不對,至少不准確

2.值類型性能更好,

  這句話要考慮情況

 

先補充一些背景資料

常見的值類型有:大部分原生類型,例如int float long 各種自己定義的結構體等等

常見的引用類型有:string 各種Class 數組(包括int[]這種的)

堆棧:在這里指的是執行堆棧

堆:在這里指的是托管堆,就是LOH+G0+G1+G2

 

讓我們先來看看第一點:兩者的區別是值類型分配在堆棧上,引用類型分配在堆上

1.假設在一個方法里面有一個語句是 var obj = new object(); 

首先 new 出來的Object將被存放在堆中

obj在堆棧上,其內容是一個指針,指向new 出來的那個Object

2.然后假設在一個方法里面有一個語句是 var i =1 ;

 這里的 i 在堆棧上, 其值是1  (int 類型)

3.類中的值類型成員,例如以下一個定義

    public class ClassA
    {
        private int i = 1;
    }

假設在一個方法里面有一個語句是 var obj = new ClassA(); 

首先 new 出來的ClassA將被存放在堆中

obj在堆棧上,其內容是一個指針,指向new 出來的那個ClassA

ClassA中的成員 i 這個時候也在堆上

假設有一個有一個其他語句使用到ClassA.i 這個i的值才會被拷貝到堆棧上(大部分默認的情況)

4.將引用類型放在堆棧上

 unsafe
            {
                var obj = stackalloc int[100];
            }

stackalloc是用來在堆棧上分配內存的keyword

上面的4個例子正好證明了 引用類型和值類型都可以存在在堆和堆棧上

不過大部分時候都是情況1和2, 所以大部分引用類型都在堆上,大部分

 

讓我們先來看看第二點:值類型性能更好

就上面的情況1,2而言

a.在取一個對象的時候,情況1先讀取obj的值, 這是一個地址,然后要重新讀取該地址的真正的對象Object

  情況2讀取obj的值,這就是真正的值了,所以相對數據比較快

b.在堆中的對象受到GC的影響,需要額外的CPU資源;(堆棧中的對象,出棧以后釋放掉了)

c.在堆中的對象需要等到GC后才被釋放,所以暫用內存時間較久

其他情況:

1.考慮一些情況,裝箱拆箱;這是值類型在堆棧和對中拷貝時特有的操作,該操作還是非常消耗資源的

  那么如果無法避免裝箱拆箱,就要考慮避免使用值類型了

2.值類型傳遞的時候每次都是值拷貝,如果某個值類型很大(例如自己定義的struct) 那么這個性能也是個問題;(而且還要考慮到堆棧有大小限制)

  所以一般情況下比較復雜的類型都只能用class

3.許多時候,引用比較都比值比較來的快,因為引用比較只要看看兩個地址是否相等

  而值比較卻要考慮里面真實的值

 

值類型和引用類型的區別其實從他們的名字上就看的出來

在傳遞值類型的時候傳遞的是真實值,這也就意味着原來的值被復制了一份到新的位置

而在傳遞引用類型的時候傳遞的是引用(地址),這里並沒有復制一份原來值的動作,因此兩個引用都指向一個對象

 

Ref

在沒有Ref的時候傳遞參數,CLR會為每個參數弄一個臨時變量出來,存儲值類型的值或者是引用類型的指針

這種情況下修改值類型或者引用類型的值不會影響到原來的值

但是修改引用類型的成員會影響到原來的值,下面兩個例子分別是修改參數成員和修改參數本身

    public class ClassA
    {
        public string Name { get; set; }
    }
    class Program
    {
         
        static void Main(string[] args)
        {
            ClassA a = new ClassA();
            Test(a);
            Console.WriteLine(a.Name);//這里會輸出mark
        }

        private static void Test(ClassA a)
        {
            a.Name = "Mark";
        }
    }
    public class ClassA
    {
        public string Name { get; set; }
    }
    class Program
    {
         
        static void Main(string[] args)
        {
            ClassA a = new ClassA();
            Test(a);
            Console.WriteLine(a.Name);//這里不會輸出Liu
        }

        private static void Test(ClassA a)
        {
            a = new ClassA() { Name = "Liu" };
        }
    }

  

在有REF的情況下傳遞參數,CLR就會把原來的變量的地址傳遞過去,如果修改了該變量會影響到原來的成員

    public class ClassA
    {
        public string Name { get; set; }
    }
    class Program
    {
         
        static void Main(string[] args)
        {
            ClassA a = new ClassA();
            Test(ref a);
            Console.WriteLine(a.Name);//這里會輸出Liu
        }

        private static void Test(ref ClassA a)
        {
            a = new ClassA() { Name = "Liu" };
        }
    }

  

 

備注1:如何確定一個對象在堆上或者是堆棧上

剛才說的都是理論,這邊是驗證

使用WinDBG+SOS附加到一個.net程序上;然后查看堆中的情況;

具體指令就不說了,大家查看一下幫助


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM