[.NET C#] REF關鍵字與ValueType、ReferenceType

  • 690
  • 0
  • C#
  • 2016-01-27

C#在撰寫Method時,我們可以在傳入參數上加上REF關鍵字,過去我對這個REF關鍵字一直不是完全瞭解,總是跟Object / Reference Type扯在一起; 加上MSDN的解釋上也滿混沌的:” ref 關鍵字會導致引數由參考加以傳遞,而非透過值。 由參考傳遞的效果,是呼叫方法中參數的任何變更,都會反映在呼叫方法中…”
這個定義的說法,似乎太過於著重於”果”,而非描繪其真實現象

C#在撰寫Method時,我們可以在傳入參數上加上REF關鍵字,過去我對這個REF關鍵字一直不是完全瞭解,總是跟Object / Reference Type扯在一起; 加上MSDN的解釋上也滿混沌的:” ref 關鍵字會導致引數由參考加以傳遞,而非透過值。 由參考傳遞的效果,是呼叫方法中參數的任何變更,都會反映在呼叫方法中…

這個定義的說法,似乎太過於著重於”果”,而非描繪其真實現象,用REF的重點在於,"拿來當作引數的物件,本身即會直接成為被參考的對象";以下我們分別來看,Reference Type與Value Type在加上REF後的差異。

Reference Type範例

class Program
    {
        public class SampleClass
        {
            public int a = 0;
        }
        static void Main(string[] args)
        {
            SampleClass i = new SampleClass(){a=5};
            Console.WriteLine("field a start as " + i.a.ToString());
            Change(i);
            Console.WriteLine("field a become as " + i.a.ToString() + " after call change()");
        }
        static void Change(SampleClass y)
        {
            y.a = 10;
            Console.WriteLine("field a change to " + y.a.ToString() +" in Change()");
            y = new SampleClass();
            Console.WriteLine("field a change to " + y.a.ToString() +" in Change()");
        }
    }

在第一個例子中我們先不加上REF來傳入參數,先看一下執行結果

在change這個方法中,我們先將欄位a設為10,接著在將y實例化為另一個SampleClass物件,而回到Main方法中,i的欄位a跟著變成了10;但為何不是呼叫change()之前的5?或者在change method最後的0?

其實,在呼叫Change前區域變數i因為是參考型別產生出來的物件,所以在Stack中的i會指向Heap中的一個位址(假設為ox0001),而這個位址的內容就是new SampleClass(){a=5}。

由於我們接著拿i來當作引數放入到change方法中,所以進到change method後,其產生一個叫y的區域變數,其內容指到i所參考ox0001。所以在change方法中,我們怎麼改y.a,實際上也就等於在改i.a;區域變數i由於跟y指向同一個heap記憶體區域,所以是在改相同的物件。

直到,在change()中的y = new SampleClass()這行,這行的意思是y變數現在改去參考另一個新初始化的SampleClass物件,假設在heap中為ox0002;這時候y參考ox0002; 而i參考還是留在ox0001,此時i跟y兩者變脫勾,在y.a做的修改,已經影響不到i了。

那REF作用為何?

如果我們在Change method中加入REF關鍵字。

class Program
    {
        public class SampleClass
        {
            public int a = 0;
        }
        static void Main(string[] args)
        {
            SampleClass i = new SampleClass(){a=5};
            Console.WriteLine("field a start as " + i.a.ToString());
            Change(ref i);
            Console.WriteLine("field a become as " + i.a.ToString() + " after call change()");
        }
        static void Change(ref SampleClass y)
        {
            y.a = 10;
            Console.WriteLine("field a change to " + y.a.ToString() +" in Change()");
            y = new SampleClass();
            Console.WriteLine("field a change to " + y.a.ToString() +" in Change()");
        }
    }

執行後

我們在method change中最後的結果為0也一併反映到變數i上了;為什麼會這樣呢?

實際上加了REF的差別,會讓Change方法中的y並不是直接指向heap上的記憶體區塊ox0001,而是指向了變數i,也就是y->i->ox0001;所以,當y執行後,就變成y->i->ox0002;也就導致i.a的值也變成0了。

Value Type範例

接著在解釋Value Type應該比較好懂了,當進到Change 方法中時,由於Value Type的內容會直接存在Heap中, 所以變數y會直接複製一份變數i的內容,接著兩者變脫勾,y 做的任何修改均於呼叫端的i沒有關係。

class Program
    {
        static void Main(string[] args)
        {
            int i = 5;
            Console.WriteLine("varible i start as "+i.ToString());
            Change(i);
            Console.WriteLine("varible i become as "+i.ToString() +" after call change()");
        }
        static void Change(int y)
        {
            y = 10;
            Console.WriteLine("varible y be set as " + y.ToString() + " in change()");
        }
    }

執行結果: 執行完Change方法後

接著我們將Change 方法改用REF方式來傳遞y參數。

class Program
    {
        static void Main(string[] args)
        {
            int i = 5;
            Console.WriteLine("varible i start as "+i.ToString());
            Change(ref i);
            Console.WriteLine("varible i become as "+i.ToString() +" after call change()");
        }
        static void Change(ref int y)
        {
            y = 10;
            Console.WriteLine("varible y be set as " + y.ToString() + " in change()");
        }
    }

執行結果

加上REF關鍵字後,Change方法中的變數y會直接指向i變數,如同y=>i (含有值5;而後被改成10) ,所以在方法中y內容的修改,實際上都等同於直接改變i的內容。

 

參考

http://www.leerichardson.com/2007/01/parameter-passing-in-c.html