首先定義一個結構html
public struct MyStruct { public int T; }express
定義一個泛型List來存放結構體,而後訪問第一個元素去修改T,輸出T:數組
List<MyStruct> arrLis =new List<MyStruct>(){new MyStruct()};post
arrLis[0].T = 100;spa
Console.WriteLine(arrLis[0].T);3d
你們猜是什麼結果?指針
很遺憾不是100,arrLis[0].T = 100;VS提示該語句有錯誤。Cannot modify the expression because it is not a variable.code
說修改的不是一個變量。htm
這是爲何呢?對象
關於這個問題咱們首先來看一下List的源碼
其實List[]被稱作索引器。索引的實現其實相似屬性,靠一對Get,Set方法來實現的。索引器其實只是C#的語法糖而已。那麼很明顯咱們上面的語句其實只是調用了get_Item方法而已,且返回值MyStruct是個值類型。因此get_Item方法返回的是一個值(value)。你也許會說,那又怎麼樣,我爲何就不能修改這個值。很不辛,在.NET中值(value)是不能被修改的,只有變量(variable)纔可以被修改,這就是爲何變量稱之爲」變量」了:)。
再看下面的代碼,咱們修改一下,把泛型List改成Array數組。
MyStruct[] arrStr =new MyStruct[1]{new MyStruct()};
arrStr[0].T = 100;
Console.WriteLine(arrStr[0].T);
你是否以爲此次賦值語句也會報錯?
其實否則,代碼順利經過編譯,運行成功。
結果輸出:100
這太奇怪啦,爲何把List改爲Array就沒有問題了呢。
讓咱們繼續查看一下源碼
看到沒,對於一維數組的訪問實際上是訪問到了這個GetValue方法。該方法的意思是使用typeReference去取到位於index位置的對象的引用,而後轉換爲Object返回。看來緣由就在這裏了,對於數組的[]索引器實際上是返回了對象的一個引用(地址),也就是至關於咱們使用Array[0]訪問的是獲得的是一個變量(variable),因此能夠直接給內部的成員變量賦值。
對於這段源碼也許不是那麼好理解,不妨看看IL。
ldelema:將位於指定數組索引的數組元素的地址做爲 & 類型(託管指針)加載到計算堆棧的頂部。
這就很清楚了,在IL裏面也清楚的顯示,操做的是對象的地址。
到這裏,Array跟List索引訪問的區別出來了,Array是返回了對象的引用,而List返回的就是對象的值(值類型對象就是內部的值,引用類型對象是引用的地址)。
還沒完,既然直接給賦值不行,那我用一個Set方法包裝起來,去設置內部變量的值如何?
public struct MyStruct {
public int T;
public void SetT(int t)
{ T = t; }
}
改造一下,加了一個SetT方法。
把List初始化語句也改一下,去掉一些語法糖,由於咱們要查IL,語法糖會影響咱們的判斷。
List<MyStruct> arrLis = new List<MyStruct>();
var myStruct = new MyStruct();
arrLis.Add(myStruct);
arrLis[0].SetT(100);
Console.WriteLine(arrLis[0].T);
以上代碼順利經過。
輸出:0
那爲何直接訪問方法就能夠呢。其實arrLis[0].SetT(100); 這也能夠算是一個語法糖。上面A段代碼到了IL層面其實就至關於下面B段代碼,IL仍是會用一個局部變量去接arrLis[0]返回的值。
List<MyStruct> arrLis = new List<MyStruct>();
var myStruct = new MyStruct();
arrLis.Add(myStruct);
var temp = arrLis[0];
temp.SetT(100);
Console.WriteLine(arrLis[0].T);
不信咱們查一下IL:
左邊是A段代碼,右邊是B段代碼:
這2段IL只有紅線畫出來的地方不同,其實就是一個變量命名不同而已。
Q4:
那上面A段代碼輸出爲何是0呢?
這個也很好理解,既然arrLis[0].SetT(100); 至關於var temp = arrLis[0]; 那麼值類型賦值操做,實際上是把右邊的值(副本)賦值給了左邊的變量,咱們用SetT來修改T的時候只是在修改temp裏面的T而已。這個不用多解釋吧。
當咱們在List裏面使用值類型的時候必定要格外當心,特別是使用結構體的時候,由於從表象上來講更像一個引用類型(結構能夠定義方法,成員變量等),不知不覺你就會用引用類型對象的慣用法去處理問題,說不定就掉坑了。因此結構體最好定義爲不可變的。
參考:
why-can-struct-change-their-own-fields