[.NET]在List.Add()時,請了解加入的item是參考位址

  • 12767
  • 0
  • 2011-11-19

[.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; }
    }



說明
其實我在程式碼的註解上,都有說明了,這邊再拉出來強調一下。

  1. 將類別初始化一個物件時,基本上就會在private memory保留一塊給這個instance,也就是上面講的參考位址。
  2. 只要這個instance還活著,沒被回收,它的參考位址基本上就不會變。
  3. List裡面是可以重複增加同一個instance的,在List中的index雖不一樣,但如果取出item來修改,那麼改的仍是該item所對應的參考位址。所以,在上面的例子,改了第二個item,其實第一個item的值也跟著改變了。
  4. Object的Equals是用來比較參考位址是否相同。
  5. List裡面的item如果要不一樣,請重新new一個instance再加入List中。

 

執行結果
image

 

結論
通常會這樣寫的理由,都是new的時候,有很多屬性是一樣的,就不如拉出來在最外面先new一個instance起來,把共同要設定的屬性設好,然後在根據不同的條件改變屬性值,在條件內就把該item加入List。在偵錯時看,可能都會覺得對,但最後的結果就是類似『覆寫』的效果,其實是因為同一個instance值一直被修改,List中的items都是存放同一個參考位址導致。

懶得每次都重新設定一堆值,可以額外做個很簡單的工廠,把固定的值在該方法裡面塞進去就可以了。


blog 與課程更新內容,請前往新站位置:http://tdd.best/