C 可變結構體中的唯讀例項成員

2021-10-10 11:22:59 字數 3622 閱讀 9490

簡單來說,還是為了提公升效能。

我們已經知道了唯讀結構體(readonly struct)和 in 引數可以通過減少建立副本,來提高**執行的效能。當我們建立唯讀結構體型別時,編譯器強制所有成員都是唯讀的(即沒有例項成員修改其狀態)。但是,在某些場景,比如您有乙個現有的 api,具有公開可訪問字段或者兼有可變成員和不可變成員。在這種情形下,不能將型別標記為 readonly (因為這關係到所有例項成員)。

通常,這沒有太大的影響,但是在使用 in 引數的情況下就例外了。對於非唯讀結構體的 in 引數,編譯器將為每個例項成員的呼叫建立引數的防禦性副本,因為它無法保證此呼叫不會修改其內部狀態。這可能會導致建立大量副本,並且比直接按值傳遞結構體時的總體效能更差(因為按值傳遞只會在傳參時建立一次副本)。

看乙個例子您就明白了,我們定義這樣乙個一般結構體,然後將其作為 in 引數傳遞:

public struct rect

}}public class sampleclass

}編譯後,類 sampleclass 中的方法 m **執行邏輯實際上是這樣的:

public float m([in] [isreadonly] ref rect value)

可變結構體中的唯讀例項成員

我們把上面的可變結構體 rect 修改一下,新增乙個 readonly 方法 getareareadonly,如下:

public struct rect

}public readonly float getareareadonly()

}此時,**是可以通過編譯的,但是會提示一條這樣的的警告:從 「readonly」 成員呼叫非 readonly 成員 「rect.area.get」 將產生 「this」 的隱式副本。

翻譯成大白話就是說,我們在唯讀方法 getareareadonly 中呼叫了非唯讀 area 屬性將會產生 「this」 的防禦性副本。用**演示一下編譯後方法 getareareadonly 的方法體執行邏輯實際上是這樣的:

[isreadonly]

public float getareareadonly()

所以為了避免建立多餘的防禦性副本而影響效能,我們應該給唯讀方法體中呼叫的屬性或方法都加上 readonly 修飾符(在本例中,即給屬性 area 加上 readonly 修飾符)。

呼叫可變結構體中的唯讀例項成員

我們將上面的示例再修改一下:

public struct rect

}public readonly float getareareadonly()

public float getarea()

public class sampleclass

public float callgetareain(in rect vector)

public float callgetareareadonly(in rect vector)

}

類 sampleclass 中定義三個方法:

第乙個方法是以前我們常見的呼叫方式;

第二個以 in 引數傳入可變結構體,呼叫非唯讀方法(可能修改結構體狀態的方法);

第三個以 in 引數傳入可變結構體,呼叫唯讀方法。

我們來重點看一下第二個和第三個方法有什麼區別,還是把它們的 il **邏輯翻譯成易懂的執行邏輯,如下所示:

public float callgetareain([in] [isreadonly] ref rect vector)

public float callgetareareadonly([in] [isreadonly] ref rect vector)

可以看出,callgetareareadonly 在呼叫結構體的(唯讀)成員方法時,相對於 callgetareain (呼叫結構體的非唯讀成員方法)少建立了一次本地的防禦性副本,所以在執行效能上應該是有優勢的。

唯讀例項成員的效能分析

效能的提公升在結構體較大的時候比較明顯,所以在測試的時候為了能夠突出三個方法效能的差異,我在 rect 結構體中新增了 30 個 decimal 型別的屬性,然後在類 sampleclass 中新增了三個測試方法,**如下所示:

public struct rect

}public readonly float getareareadonly()

public float getarea()

public decimal number1

public decimal number2

//...

public decimal number30 public class sampleclass

[benchmark(baseline = true)]

public float donormalloop()

return result;

}[benchmark]

public float donormalloopbyin()

return result;

}[benchmark]

public float doreadonlyloopbyin()

return result;

}public float callgetarea(rect vector)

public float callgetareain(in rect vector)

public float callgetareareadonly(in rect vector)

}在沒有使用 in 引數的方法中,意味著每次呼叫傳入的是變數的乙個新副本; 而在使用 in 修飾符的方法中,每次不是傳遞變數的新副本,而是傳遞同一副本的唯讀引用。

donormalloop 方法,引數不加修飾符,傳入一般結構體,呼叫可變結構體的非唯讀方法,這是以前比較常見的做法。

donormalloopbyin 方法,引數加 in 修飾符,傳入一般結構體,呼叫可變結構體的非唯讀方法。

doreadonlyloopbyin 方法,引數加 in 修飾符,傳入一般結構體,呼叫可變結構體的唯讀方法。

使用 benchmarkdotnet 工具測試三個方法的執行時間,結果如下:

method mean error stddev ratio ratiosd

donormalloop 2.034 s 0.0392 s 0.0348 s 1.00 0.00

donormalloopbyin 3.490 s 0.0667 s 0.0557 s 1.71 0.03

doreadonlyloopbyin 1.041 s 0.0189 s 0.0202 s 0.51 0.01

從結果可以看出,當結構體可變時,使用 in 引數呼叫結構體的唯讀方法,效能高於其他兩種; 使用 in 引數呼叫可變結構體的非唯讀方法,執行時間最長,嚴重影響了效能,應該避免這樣呼叫。

總結當結構體為可變型別時,應將不會引起變化(即不會改變結構體狀態)的成員宣告為 readonly。

當僅呼叫結構體中的唯讀例項成員時,使用 in 引數,可以有效提公升效能。

readonly 修飾符在唯讀屬性上是必需的。編譯器不會假定 getter 訪問者不修改狀態。因此,必須在屬性上顯式宣告。

自動屬性可以省略 readonly 修飾符,因為不管 readonly 修飾符是否存在,編譯器都將所有自動實現的 getter 視為唯讀。

不要使用 in 引數呼叫結構體中的非唯讀例項成員,因為會對效能造成負面影響。

C 8 可變結構體中的唯讀例項成員

在之前的文章中我們介紹了 c 中的 唯讀結構體 readonly struct 1 和與其緊密相關的in引數 2 今天我們來討論一下從 c 8 開始引入的乙個特性 可變結構體中的唯讀例項成員 當結構體可變時,將不會改變結構體狀態的例項成員宣告為readonly 簡單來說,還是為了提公升效能。我們已經...

可變長陣列 柔性陣列結構體成員 可變長結構體

可變長陣列是c99相較c89新增的乙個特性。1.可變長陣列無鏈結性,即只在 塊內作用 函式體內或者區域性 塊內 2.可變長陣列不可初始化。3.陣列長度在生存週期不可更改。即n的值不影響str的長度。4.可變長陣列作為函式形參的時候,中使用 其本質是指標。形參中的陣列還可以使用型別限定詞const v...

結構體中的成員對齊

關於結構體中成員對齊的總結 a.結構體中,結構體成員要對齊到其對齊值倍數的位址上,對齊值為min 成員型別對齊值,編譯器結構成員對齊值b.結構體本身的對齊值為其所有成員中最大的對齊值。c.結構體本身要對齊到其對齊值倍數的位址上。d.結構體中的結構體要對齊到其對齊值倍數的位址上。e.結構體成員在記憶體...