跳到主要內容

方法傳值(Passing Parameters )的方式


在上一篇Reference Type & Value Type中談到

Type分實質類型(Value Type) 與參考類型(Reference Type)

類型的分類除了以上兩種外還有另外一種較少看到或使用的指標類型(Pointer Type)

但指標類型跟我們最後要探討的目標比較無關,故在此先不予以討論。


C#中我們可以在方法參數的定義中

定義此方法是傳值(passing by value)方式還是傳址方式(passing by reference)

若是想要以傳址方式進行參數傳遞,

那在參數前面應該使用關鍵字(keyword) out 或是 ref


我們先以一個簡單的例子探討C#中方法傳值(Passing Parameters)的規則

        public static void Main()
        {
            int i = 2;
            Change(i);
            Console.WriteLine(i);     //Output 2
            Change(ref i);
            Console.WriteLine(i);     //Output 4
            Console.ReadKey();
        }
        public static int Change(int x)
        {
            var calculate = x*=x;
            // Output 4
            Console.WriteLine($"在方法內計算的結果為{calculate}");
            return calculate;
        }
        public static int Change(ref int x)
        {
            var calculate = x *= x;
            // Output 4
            Console.WriteLine($"在方法內計算的結果為{calculate}");
            return calculate;
        }


  • 第一種情況 -- 使用預設的方式進行參數傳遞


例子中我們可以明白到第一次使用int變數i的時候

雖然在方法內做完計算,但方法結束後變數i內部裝的仍然是原本的 2

經由上一篇的探討中我們可以明白因為intvalue type

方法中的參數實際上做的是將原本i的值完完整整的複製成一個獨立個體並用這個副本傳遞

所以不難理解在方法內對獨立副本怎麼進行更動,都不會影響到外部本來的變數值。


  • 第二種情況--使用ref傳址的方式進行參數傳遞


因為我們使用了關鍵字ref,所以這時候參數傳遞的方式不再是複製變數的值傳入

而是傳入參考(變數位址)本身,因為是直接對位址的內容物做事

所以這時候在方法內部的更動可以影響外部。


小試身手後,我們進入今天的重點,看看那在Class上的運用呢?

        public static void Main()
        {
            var person = new Person { Name = "NiceMan" };
            LearningToBad(person);
            Console.WriteLine(person.Name); //Output BadMan
            Console.ReadKey();
        }
        public static void LearningToBad(Person person)
        {
            person.Name = "BadMan";
        }

很直覺的可以了解到因為Person是參考類型,我們已經對址內的內容物做改變,

所以即使以void方法去執行,也會對外部的實體Instance影響。

再看下一種情況,我們更動LearningToBad方法內的陳述式,

        public static void Main()
        {
            var person = new Person { Name = "NiceMan" };
            LearningToBad(person);
            Console.WriteLine(person.Name); //Output NiceMan
            Console.ReadKey();
        }
        public static void LearningToBad(Person person)
        {
            person = new Person { Name = "BadMan" };
        }

奇怪的事情發生了,在內部讓person重生為一個壞人,但為什麼最終person沒有學壞?

原來是因為LearningToBad的內部區域變數person只是址的值。


person = new Person { Name = "BadMan" };


我們只是重新指派變數person的值為另一個位址的值,

所以理所當然的外部原本的變數person還是指向原本的Instance,

當然外部就不會有所改變

我們再重新更改方法參數的傳遞方式加上ref,試著將完整的址傳進參數裡

                public static void Main()
        {
            var person = new Person { Name = "NiceMan" };
            LearningToBad(ref person);
            Console.WriteLine(person.Name); //Output BadMan
            Console.ReadKey();
        }
        public static void LearningToBad(ref Person person)
        {
            person = new Person { Name = "BadMan" };
        }


果然,外部的Instance重新被指向了新的壞人,你看看你,很壞唷。


綜觀以上,我們可以得知結論

方法參數的傳遞很容易混淆的是,不論參數類型是實質還是參考型別

方法參數都是以傳值(passing by value)方式傳遞

實際做的事情都是將原本的變數複製一份傳進方法內執行


而這時參數類型的差別只在於變數的值本身不同而已

實質類型變數的值是值本身,而參考型別的變數的值是位址的值

若是要以傳址方式傳遞參數,必須在定義方法參數時加上ref或者是out 關鍵字,

這時候在方法內執行的變數就是外部的變數本身。


小小心得

若有謬誤

請以下留言更正指教



參考https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/passing-parameters

留言

這個網誌中的熱門文章

C# 委派(Delegate) (一)

今天要介紹的是C#的委派 Delegate Delegate的發展由函數指標( funtion pointer )而來,可以說Delegate是函數指標中的語法糖( Syntactic sugar )也不為過,在Java中則是Sam類。早期在C# 1.0的時候,匿名函數與Lambda表達式還沒有被發展出來,C與C++有函數指標來負責打包函數( function ),那C#呢?我們都知道物件導向程式OOP中所有的東西都該是物件,所以C#就創造了一個類別專門來打包函數。 本篇開始會依序從C#的發展順序介紹委派 Delegate、Anonymous Function、 Lambda  Expression、Func<>、Action<> 委派含括很大的範圍,故可能會花上幾篇的篇幅來一一介紹。   1.Delegate 首先來介紹的是最初的委派( Delegate )。月亮是外國的圓,我們先來看看微軟( MicroSoft )怎麼介紹自家的的委派-- A delegate is a reference type that can be used to encapsulate a named or an anonymous method -- 一種用來封裝具名或匿名方法的參考 類型 。至於為什麼封裝函數叫做委派呢,不如試想,原本有一個流程方法(Sop)是要由A公司去完成的,但我們將他外包給委派給B公司去做,把方法流程外包的行為就叫做委派,這樣不難理解吧。 廢話不多說,立刻來看委派是如何被宣告( declare )以及使用的。 委派的宣告: public delegate int MyDelegete(int x); 我們可以注意到一個委派類型的宣告簽章( Signatures ),就像是類似宣告一個方法般,給他回傳型態、名稱、參數就完成宣告了,所以我們先做的是宣告一個回傳型態為int、委派類型名稱為Mydelegate、參數型態為int的委派宣告。好啦,宣告完成拉接下來是封裝方法。既然委派是一種類型,所以我們必須將他實作( implement )。 挖哩怎麼報紅了,原來委派類型變數的宣告必須在建構子參數中給與這個符合此委派類型簽章的方法,於是我們必須相應而生出一個回傳型態、參數型態皆

C# 委派(Delegate) (二) - Anonymous Function、Lambda Expression

上一篇中 C# 委派(Delegate) (一) 介紹了Delegate類基本觀念、宣告方式與使用方式,本篇將照著C#中委派的發展史中,繼續介紹 Anonymous Function、Lambda Expression。 Anonymous Function 還記得上篇的範例中,我們每宣告一個委派( delegate )的變數( variable )就要寫一個跟委派相同簽章的 具名 方法對應是吧? 但是這個專用來給委派變數宣告用 具名 方法, 具名 不是顯得很多餘嗎,我們似乎不太需要知道這個方法叫什麼名字啊。 public delegate int MyDelegate(int x); static void Main(string[] args) { //AddOne是專門寫來給委派變數使用的方法,這個具名方法只用一次,從此以後AddOne沒在被用過。 MyDelegate mydelegate = new MyDelegate(AddOne); } public static int AddOne(int number) { return ++number; } 所以在C#2.0中貼心的微軟發展了一套專門給委派變數宣告專用的語法糖( Syntactic sugar )叫做匿名方法( Anonymous Function )。 public delegate int MyDelegate(int x); static void Main(string[] args) { //不需要再寫一個AddOne方法 MyDelegate mydelegate = delegate (int number) { return number + 1; }; } 所以在C#2.0中只要如上宣告就完成了委派變數的宣告。匿名方法的變數宣告中,左式必須要使用明確的委派類別來宣告(這邊使用 MyDelegate ),不能使用 var 關鍵字,如果堅持要使用var關

Reference Type & Value Type

相信有仔細看過前一篇文章後 對於C#中Value Type 與Ref  Type有了一些初步的了解(才怪) 今天直接 Passing By Value 與 Passing By Reference來探討Value Type 與Ref  Type的差別 話不多說舉個例子 1.參考型別(Reference Type) 假設某個學校出了一個模範生小明,老師希望以後所有的學生都像小明一樣聰明 所以把小明克隆了一個新的取名叫做小華 看以下程式碼 public static void Main() { var student1 = new Stutent(); student1.Name = "阿明"; var student2 = student1; student2.Name = "小華"; Console.WriteLine(student1.Name); } class Stutent { public string Name{get;set;} } 猜猜Output出什麼呢 答案是小華 什麼? 為什麼? 靠北不是把令了一個新的變數student2來裝student1了嘛 那為什麼把新生出來的student2的名字取為小華 導致原本的小明也變成小華了呢 老師感到疑惑,小明到底跑哪去了呢,聰明的老師立刻打開電腦 Google了Msdn才發現   阿~!  小華原來就是小明 還裝?! 因為  Class Student是一個 參考型別 參考型別的變數 承載的是記憶體的位置 將一個新的座位,指派給小明, 並把新座位的主人改名為小華, 然後在請原本小明座位上的人大聲喊出自己的名字 小明 當然就會說 "我是小華" 囉 仔細看看這段程式碼 var student1 = new Stutent(); 再把它中文翻譯 我宣告了一個變數student1 並指派為一個新的學生 口語邏輯上