前言
簡單整理一下struct。
正文
struct
對於struct 而言呢,我們往往會拿class作為對比,但是呢,我們在初學階段用class來替代struct,struct的存在感越來越低了。
那麼是什麼原因使我們經常使用struct呢?我感覺很簡單的一句話就是struct能做的class都能做,struct不能做的,class 也能做,這就是問題關鍵了。
那麼先來看下他們的對比:
1、結構是值型別,它在棧中分配空間;而類是引用型別,它在堆中分配空間,棧中儲存的只是引用。
2、結構型別直接儲存成員資料,讓其他類的資料位於堆中,位於棧中的變數儲存的是指向堆中資料物件的引用。
-
結構不支援繼承。
-
結構不能宣告預設的建構函式。
-
結構型別中不能設定預設值。
從第二點中可以明白結構型別中,不一定儲存的一定是值,還可能是引用,這就打破了初學的時候誤以為結構型別只能儲存值型別,還可能是引用物件的引用,如下:
static void Main(string[] args)
{
var parent = new Parent(30,"張大大");
var zhangsan = new Student(1,"張三",parent);
zhangsan.age = 10;
}
struct Student {
public Student(int age, string name,Parent parent)
{
this.age = age;
this.name = name;
this.parent = parent;
}
public int age { get; set; }
public string name { get; set; }
public Parent parent { get; set; }
}
struct Parent {
public Parent(int age, string name)
{
this.age = age;
this.name = name;
}
public int age { get; set; }
public string name { get; set; }
}
在Student 結構中,我們也可以去複製引用。
第三點很好理解,第四點表示我們不能去自己宣告預設建構函式。如:
public Student() {
}
那麼我們什麼時候使用struct呢?
那麼要從struct 優點出發,struct 是值型別,當離開作用域的時候,那麼對垃圾回收是有好處的。
同樣,因為struct 是值型別,分配到堆上,如果值型別過大,這會大量佔用到堆的空間,所以我們的資料比較下。
當有大量的賦值語句的時候,那麼我們也應該避開struct,因為賦值值型別中將會拷貝全部,而不是引用。
根據上訴,實用場景為:
對於點、矩形和顏色這樣的輕量物件,假如要宣告一個含有許多個顏色物件的陣列,則CLR需要為每個物件分配記憶體,在這種情況下,使用結構的成本較低;
從上總結出,struct可以在一些以資料為主的場景中使用,且資料量不大的情況。
struct 作為引數
在介紹readonly 之前,先介紹一下,和ref 還有out 其名的in,不是別的in哈。
static void Main(string[] args)
{
int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument); // value is still 44
}
static void InArgExample(in int number)
{
// Uncomment the following line to see error CS8331
//number = 19;
}
這裡的in 的作用是可以引用readonlyArgument,但是隻讀,不能修改number的值。那麼這有什麼用呢?我直接不設定值不就可以嗎?或者說我起碼設定一個readonly 這總行吧。
而我們知道in 有不能用於非同步方法,賦值消耗也不大形參,那我要這個引用有啥用?關鍵就在於我們自定義的struct還是大有好處的,struct 是我們自定義的結構型別,這個比較大,那麼這就是一個struct的突破點了,傳值的時候可以傳遞struct。
下面介紹readonly 這個是為了安全,做為一個readonly,我們首先就要區分的是const,const 是編譯性,而readonly是執行時。這個可以百度,在此就不做過多的介紹。
通過readonly struct 還有 in,那麼可以建立防禦性副本。
這裡值得注意的是,官網提到這樣一句話:
除非使用 readonly 修飾符宣告 struct或方法僅呼叫該結構的 readonly 成員,否則切勿將其作為 in 引數傳遞。 不遵守該指南可能會對效能產生負面影響,並可能導致不明確的行為
官網給出了一個這樣的例子:
int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument); // value is still 44
void InArgExample(in int number)
{
// Uncomment the following line to see error CS8331
//number = 19;
}
並且說明了一段這樣的話:
在首次檢查時,你可能認為這些訪問是安全的。 畢竟,get 訪問器不應該修改物件的狀態。 但是沒有強制執行的語言規則。
它只是通用約定。 任何型別都可以實現修改內部狀態的 get 訪問器。
如果沒有語言保證,編譯器必須在呼叫任何未標記為 readonly 修飾符的成員之前建立引數的臨時副本。
在堆疊上建立臨時儲存,將引數的值複製到臨時儲存中,並將每個成員訪問的值作為 this 引數複製到堆疊中。
在許多情況下,當引數型別不是 readonly struct,並且該方法呼叫成員未標記為 readonly 時,這些副本會降低效能,
使得按值傳遞比按只讀引用傳遞速度更快。 如果將不修改結構狀態的所有方法標記為 readonly,編譯器就可以安全地確定不修改結構狀態,並且不需要防禦性複製。
struct 作為出參
那麼上面討論了引數傳遞的問題,那麼接下來討論一下,引數返回的問題。
比如說返回了:
var a=getANumber();
private static int getANumber(){
var b=1;
return b;
}
那麼其實這個a的值怎麼獲取的呢?是b的值賦值給a。
但是對於比較大的struct,用這種賦值的方式,就比較消耗cpu和記憶體了。
那麼可以使用ref來返回。
var a=getANumber();
private static ref int getANumber(){
var b=1;
return ref b;
}
這樣b的引用傳遞給了a。
如果你希望返回的引數不可改變,那麼你可以這樣:
var a=getANumber();
private static ref readonly int getANumber(){
var b=1;
return ref b;
}
那麼這個時候有人就奇怪了,為啥ref還要 readonly 這東西呢?
舉個例子:
public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
for (int i = 0; i < matrix.GetLength(0); i++)
for (int j = 0; j < matrix.GetLength(1); j++)
if (predicate(matrix[i, j]))
return ref matrix[i, j];
throw new InvalidOperationException("Not found");
}
這個例子返回的是陣列的一部分,如果改了這個值,那麼陣列裡面的值不就改變了嗎。
可能我這樣說,加上官網這個例子不到位,可能沒能表達明白。再來一個自己寫的例子:
static void Main(string[] args)
{
var matrix =new Student[1];
matrix[0] = new Student(20,"張三",new Parent());
ref Student result =ref Find(matrix);
result.age = 10;
Console.WriteLine(matrix[0].age);
Console.WriteLine(result.age);
Console.ReadLine();
}
static ref Student Find(Student[] matrix )
{
return ref matrix[0];
}
struct Student {
public Student(int age, string name,Parent parent)
{
this.age = age;
this.name = name;
this.parent = parent;
}
public int age { get; set; }
public string name { get; set; }
public Parent parent { get; set; }
}
這裡列印出來兩個都是10。
如果給Find 加上readonly,那麼要這樣寫
ref readonly Student result =ref Find(matrix);
Student 自然不能再進行賦值。
結
上述只是個人整理和一點點個人理解,如有不對望指出。下一節,整理c# 裝箱和拆箱,介紹一下生命週期。