首頁 > 軟體

C#中的參照型別以及特殊參照型別詳解

2022-07-31 14:02:01

基本

哪些屬於參照型別

類(object,string),介面、陣列、委託

參照型別分配在哪裡

  • 參照型別變數位於執行緒棧。
  • 參照型別範例分配在託管堆上。
  • 當參照型別範例的大小小於85000bytes,被分配在GC堆上,當大於或等於85000bytes,被分配在LOH(Large Object Heap)上。

變數(Variable),物件(Object),範例(Instance)

變數:
變數分配線上程棧上。
變數可以是值型別,也可以是參照型別。
當變數是參照型別時,包含了對物件的參照(記憶體地址),也叫做"物件參照"。

物件:
對類、介面、委託和陣列等的一個抽象描述。

範例:
在堆上建立的物件,稱為物件範例。

參照型別沒有new意味著什麼?

Object a = null;
Console.WriteLine(a.ToString());
執行報錯"未將物件參照設定到物件範例"。
意思是,線上程棧上建立的變數a沒有指向到堆上的物件範例。

託管堆上的垃圾回收

GC會遍歷所有託管堆上的物件,按照一定的遞迴遍歷演演算法,對那些沒有被參照的不可存取物件實施回收。

new的背後發生了什麼

    class Program 
    { 
        static void Main(string[] args) 
        { 
            Person p; 
            p = new Person(20); 
        } 
    }

    public class Person 
    { 
        public int _age;

        public Person(int age) 
        { 
            _age = age; 
        }

        public Person() 
        { 
            
        } 
    }

另外,參照型別的值,比如這裡的參照型別Person中的值_age也被分配在託管堆上。   

執行緒棧上的2個變數參照同一個物件範例的記憶體地址

執行緒棧上的2個變數參照同一個物件範例的記憶體地址,改變其中一個變數的值會影響到另外一個變數。

    class Program 
    { 
        static void Main(string[] args) 
        { 
            Person p1 = new Person(20); 
            Person p2 = p1; 
            Console.WriteLine("沒有改變時p2的年齡是:" + p2._age + "歲"); 
            p1._age = 18; 
            Console.WriteLine("改變p1的值,p2的年齡也被改變了,現在是:" + p2._age + "歲,真好,年輕了!"); 
            Console.ReadKey(); 
        } 
    }

    public class Person 
    { 
        public int _age;

        public Person(int age) 
        { 
            _age = age; 
        }

        public Person() 
        { 
            
        } 
    }

string型別是特殊的參照型別

特殊性體現在

從應用角度體現了值型別語意,從記憶體角度實現為參照型別儲存,位於託管堆。

什麼是string

可以看作是char的集合char[]    

string建立與範例化

string str = "Hello";

以下錯誤
String str = new String("Hello");
因為System.String沒有提供此建構函式

以下可以
Char[] cs = {'a', 'b','c'};
String strArr = new String(cs);
但很少使用這種方式。

字串的恆定性Immutability

是指字串一經建立,就不可改變。
字串一旦建立,在託管堆上分配一塊連續的記憶體空間。

恆定性的好處:
對String物件的任意操作,不會改變原字串。
操作字串不會出現執行緒同步的問題。
成就了字串駐留。

恆定性的不足:
因為恆定性,對字串的任何操作,比如字串比較,字串連結,字串格式化等都會建立新的字串,這樣造成記憶體與效能的雙重損耗。如下:

public static void Main() 
{ 
    string str = "This is a test about immuntablility of string type."; 
    Console.WriteLine(str.Inseert(0,"Hi").Substring(19).ToUpper()); 
    Console.WriteLine(str); 
}

由於Insert,Substring,ToUpper這些方法,都會建立出新的臨時字串,而這些新的字串不被其他程式碼參照的時候,就會被垃圾回收,造成效能上的損失。

恆定性的前提,String為密封類:

public sealed class String:IComparable, ICloneable,IConvertible,IComparable<string>,IEnumerable<char>,IEnumerable,IEquatable<string>

字串駐留String Interning

MSDN對於字元駐留的定義:公共語言執行庫通過維護一個雜湊表(Hash Table)來存放字串,該表成為拘留池,也叫駐留池。

字串駐留彌補了恆定性的不足:
對於相同的字串,CLR不會不會為其分配記憶體空間,而是共用同一記憶體。
CLR內部維護了一個雜湊表HashTable來管理其建立的大部分String物件。key是string本身,value是string對應的記憶體地址。

駐留的2個靜態方法:
public static string Intern(string str);
當str位於作為key位於CLR的駐留池時,返回對str的參照,否則將str字串新增到hash table中,作為key,並返回參照。

public static string IsInterned(string str);
當str位於作為key位於CLR的駐留池時,返回對str的參照,否則返回null參照,也不新增到hash table中。

字串駐留是程序級的

可以跨應用程式域AppDomain而存在,駐留池在CLR載入時建立,分配在System Domain中,被程序所有AppDomain所共用,其生命週期不受GC控制。

例子1:

        static void Main(string[] args) 
        { 
            string strA = "ab"; 
            string strB = "ab"; 
            Console.WriteLine(ReferenceEquals(strA,strB)); 
            string strC = "a"; 
            string strD = strC + "b"; 
            Console.WriteLine(ReferenceEquals(strA, strD)); 
            strD = String.Intern(strD); 
            Console.WriteLine(ReferenceEquals(strA,strD)); 
            Console.ReadKey(); 
        }

返回:

true
false
true

分析:

  • strA與strB內容相同,在hash table中的key相同,對應了一樣的參照地址,所以返回true。
  • strD的內容雖然與strA相同,但由於是動態生成的,不會把hash table中key為ab的參照地址賦值給strD,所以strA與strD參照地址不一樣,返回false。
  • strD = String.Intern(strD);手動對strD實施駐留,並行現hash table中已經有了ab這個key,就把對應的參照地址賦值給了strD,這樣,strA與strD參照地址相同,返回true。

例子2:

        static void Main(string[] args) 
        { 
            string s1 = "abc"; 
            string s2 = "ab"; 
            string s3 = s2 + "c"; 
            Console.WriteLine(string.IsInterned(s3) ?? "null"); 
            Console.WriteLine(ReferenceEquals(s1,s3)); 
            Console.ReadKey(); 
        }

返回:

abc
false   

分析:

  • string.IsInterned(s3)對s3進行手動駐留,發現hash table中abc這個key,於是,就返回abc的參照地址。但並沒有把參照地址賦值給s3。
  • s1和s3的參照地址還是不一樣,返回false。

例子3:

        static void Main(string[] args) 
        { 
            string strA = "abcdef"; 
            string strC = "abc"; 
            string strD = strC + "def"; 
            Console.WriteLine(ReferenceEquals(strA,strD)); 
            string StrE = "abc" + "def"; 
            Console.WriteLine(ReferenceEquals(strA,StrE)); 
            Console.ReadKey(); 
        }

返回:

False,       
true

分析:

  • 因為strD = strC + "def"中strD的內容雖然與strA想同,但因為是動態生成的,不會被新增到hash table中,所以參照地址不一樣,返回false。
  • "abc"+"def在IL中呈現為abcdef,不是動態生成的,並且發現hash table中已經有了abcdef這個key,於是就把對應的參照地址賦值給了strE,這樣strA和StrE就有了相同的參照地址,返回true。如圖:

例子4:

        static void Main(string[] args) 
        { 
            string s = "abc"; 
            //IsInterned()獲取字串變數的參照 
            Console.WriteLine(string.IsInterned(s) ?? "null"); 
            Console.ReadKey(); 
        }

返回

"abc"。

分析:通過string s = "abc"使得hash table中有abc這個key,當進行string.IsInterned(s)手動駐留判斷的時候,發現有abc這個key,就把對應的參照地址返回。

例子5:

    class Program 
    { 
        static void Main(string[] args) 
        { 
            string s = "ab"; 
            s += "c"; 
            //IsInterned()獲取字串變數的參照 
            Console.WriteLine(string.IsInterned(s) ?? "null"); 
            Console.ReadKey(); 
        } 
    }

返回

null

分析:通過string s = "ab";使得hash table中有了ab這個key,s += "c"是動態拼接,不會把abc放到hashtable中,所以當通過string.IsInterned(s)手動駐留判斷的時候,發現沒有abc這個key,就返回null。

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對it145.com的支援。如果你想了解更多相關內容請檢視下面相關連結


IT145.com E-mail:sddin#qq.com