1.概要
在工作中大家用到引用類型是非常多的,大家都知道引用類型在使用過程中傳遞的是對象引用并不會發生整個對象復制。而值類型在傳遞的過程中就不一樣了,我曾經在編寫代碼時希望通過值類型來壓低應用程序的內存占用,在高并發的情況大量的對象需要在程序里流轉這個時候看內存監測的時候會發現,內存并沒有變少。雖說值類型能提供很多好處但有一個缺點就是會發生復制,那么如何規避復制這個缺點呢?我們往下看(本文只是簡略的分享,實際上要把這一整塊講明白幾千字是遠遠不夠的)。
2.詳細內容
2.1 ref
在繼續了解C#7.0的ref特性需要了解一些前置知識點,首先是變量和值的區別。
(下圖)變量包含內容:
變量名稱
編譯時類型
當前值
變量的聲明本質是在內存中開辟一段內存空間,給變量x賦值相當于是覆蓋了之前的值。當變量類型是引用類型時,控件里的值不再是對象本身。而是對象的引用,就是通過內存地址找到對象。(如果加上ref關鍵字,ref的引用和對象引用是不同的概念。通過值傳遞對象引用和通過引用傳遞變量是不同的。)
當把某個變量值復制給另外一個變量時,只是這個值本身發生了復制。這兩個變量依然是獨立的,之后任何一個變量的值修改不會影響另外一個變量。
這種方式的值復制,和調用方法時對值參數的操作是相同的:方法實參的值被復制到了另一個新的空間中。
而ref參數的行為與此不同。使用ref參數,不會創建開辟新的空間,而是調用放提供一個現有的包含初始值的空間。可以理解為一個空間同時被兩個地址指向:一個是調用方使用的該變量的表示,另一個是形參的名稱。
如果在方法中修改了ref參數的值,即修改了紙上的現有值。當方法返回時,修改的結果就會反回給調用方,因為修改的是同一個命名空間的值。
2.2 ref readonly
前面提到的變量都是可寫變量,以下兩個獨立場景中,只允許ref可寫變量就顯得有些不足了。
可能需要給某個只讀字段添加引用地址,避免復制以提升效率。
可能需要只允許通過ref變量進行只讀訪問。
C#7.2加入了ref readonly解決了上述問題。ref局部變量可以使用readonly進行修飾,得到的結果自然是只讀的,就像只讀字段一樣。不能為只讀變量賦新值,如果它是結構體,則不能修改任何字段或者調用屬性的setter方法。
代碼示例:
public readonly struct Juster{public int Age { get; }public Juster(int age) => Age = age;}
使用readonly的兩處都需要協作:如果調用一個帶有ref readonly返回的方法或者索引器,并且需要將結果保存到一個局部變量中,那么這個局部變量必須由ref readonly修飾。
internal class Class1{static readonly int field = DateTime.UtcNow.Second;static ref readonly int GetJusterTime() => ref field;public void Test() {ref readonly int local = ref GetJusterTime();Console.WriteLine(local);}}
2.3 in參數
C#7.2為方法參數加入了in修飾符,該修飾符的使用方式與ref、out相同,但目的不同。一個帶有in修飾符參數,可以通過引用傳遞避免復制提升效率,同時可以保證參數值不被修改。在方法內部,in參數的行為類似于ref readonly局部變量。該變量依然是由調用方傳入的一個內存地址,因此要保證方法不會被修改值,否則修改結果回影響調用方,這樣就違背了in參數的意義。
安全的使用使用in參數:
public static double PublicMethod(Juster juster) {double result = GetScale(in juster);return result + result;}private static double GetScale(in Juster input) => input.Age * input.Age;
這種方式可以防止參數被意外修改,因為方法是私有的,我們可以檢查所有調用方,確定它們不會傳遞哪些在方法執行時可能被修改的參數。在方法GetScale被調用時,每個結構體只會被復制一次,復制之后私有方法調用時都是別名。這樣就把自己的代碼和其他線程中調用方的任何修改,或者其他方法的副作用隔離開來了。
使用建議:
只有確定性能提升客觀,采用in參數,例如使用大型結構體。
在公共api中盡量避免使用in參數,除非即便參數值發生變化,方法也能正確執行。
可以考慮通過公共方法作為防止參數被修改的外部屏障,然后再內部私有方法中使用in參數來減少復制。
對于采用in參數的方法,在調用時考慮顯式給出in修飾符。
性能相關測試
https://cloud.tencent.com/developer/article/1402446
https://www.cnblogs.com/BeanHsiang/p/8687780.html