[.NET]在List.Add()時,請了解加入的item是參考位址
前言
在測試同事的程式時,發現了奇怪的現象,為什麼有些集合裡面,明明應該每個item都不一樣,出來結果的值卻又全都一樣。第一個想法就是複製貼上後,沒改到變數。後來幫忙trace了一下code,發現不是這樣,而是對object reference觀念上的問題,導致很多地方都有同樣的狀況,這篇文章會舉個簡單的例子來說明List加item時,其實加的是參考位址。
範例
class Program
{
static void Main(string[] args)
{
var people = new List<Person>();
//new起來person的instance時,Id為0, Name為null,假設其reference位址為10
var person = new Person();
var firstCondition = true;
var secondCondition = true;
if (firstCondition)
{
//將reference位址為10的person instance,Id改為1, Name改為"91"
person.Id = 1;
person.Name = "91";
//將reference位址為10的instance,加到people的index為0的位置
people.Add(person);
}
if (secondCondition)
{
//將reference位址為10的person instance,Id改為2, Name改為"92"
person.Id = 2;
person.Name = "92";
//將reference位址為10的instance,加到people的index為1的位置
people.Add(person);
}
//這時people裡面的兩個item,都是reference位址為10的instance,兩個的Id當然都是2, Name為"92"
foreach (var item in people)
{
Console.WriteLine("id: {0}, Name: {1}", item.Id.ToString(), item.Name);
}
//當修改people的第二個item時,實際上是改reference位址為10的instance,所以將其Id改為100,Name改為"100"時,List裡面的兩個item,是同一個reference位址,所以兩個item的值都會變
if (people.Count > 1)
{
people[1].Id = 100;
people[1].Name = "100";
}
foreach (var item in people)
{
Console.WriteLine("id: {0}, Name: {1}", item.Id.ToString(), item.Name);
}
//最後直接用Object.Equals()進行比較,可以確定people[0]與people[1]的位址相同
var isSame = people[0].Equals(people[1]);
Console.WriteLine("[0]與[1] reference是否相等: {0}", isSame.ToString());
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
說明
其實我在程式碼的註解上,都有說明了,這邊再拉出來強調一下。
- 將類別初始化一個物件時,基本上就會在private memory保留一塊給這個instance,也就是上面講的參考位址。
- 只要這個instance還活著,沒被回收,它的參考位址基本上就不會變。
- List裡面是可以重複增加同一個instance的,在List中的index雖不一樣,但如果取出item來修改,那麼改的仍是該item所對應的參考位址。所以,在上面的例子,改了第二個item,其實第一個item的值也跟著改變了。
- Object的Equals是用來比較參考位址是否相同。
- List裡面的item如果要不一樣,請重新new一個instance再加入List中。
結論
通常會這樣寫的理由,都是new的時候,有很多屬性是一樣的,就不如拉出來在最外面先new一個instance起來,把共同要設定的屬性設好,然後在根據不同的條件改變屬性值,在條件內就把該item加入List。在偵錯時看,可能都會覺得對,但最後的結果就是類似『覆寫』的效果,其實是因為同一個instance值一直被修改,List中的items都是存放同一個參考位址導致。
懶得每次都重新設定一堆值,可以額外做個很簡單的工廠,把固定的值在該方法裡面塞進去就可以了。
blog 與課程更新內容,請前往新站位置:http://tdd.best/