本文將介紹以下內容:
按值傳遞與按引用傳遞深論
ref和out比較
參數應用淺析
1.引言
接上回《第九回:品味類型---值類型與引用類型(中)-規則無邊》中,對值類型和引用類型的討論,其中關於string類型的參數傳遞示例和解釋,引起園友的關注和討論,可謂一石激起千層浪。受教於裝配腦袋的深切指正,對這一概念有了相當進一步的了解,事實證明是我錯了,在此向朋友們致歉,同時非常感謝大家的參與,尤其是裝配腦袋的不倦相告。
因此,本文就以更為清晰的角度,把我理解有誤的雷區作做以深入的討論與分析,希望通過我的一點點努力和探討至少對如下幾個問題能有清晰的概念:
什麼是按值傳遞?什麼是按引用傳遞?
按引用傳遞和按引用類型參數傳遞的區別?
ref與out在按引用傳遞中的比較與應用如何?
param修飾符在參數傳遞中的作用是什麼?
2.參數基礎論
簡單的來說,參數實現了不同方法間的數據傳遞,也就是信息交換。Thinking in Java的作者有過一句名言:一切皆為對象。在.NET語言中也是如此,一切數據都最終抽象於類中封裝,因此參數一般用於方法間的數據傳遞。例如典型的Main入口函數就有一個string數組參數,args是函數命令行參數。通常參數按照調用方式可以分為:形參和實參。形參就是被調用方法的參數,而實參就是調用方法的參數。例如:
using System;
public class Arguments
{
public static void Main(string [] args)
{
string myString = "This is your argument.";
//myString是實際參數
ShowString(myString);
}
private void ShowString(string astr)
{
Console.WriteLine(astr);
}
}
由上例可以得出以下幾個關於參數的基本語法:
形參和實參必須類型、個數與順序對應匹配;
參數可以為空;
解析Main(string [] args),Main函數的參數可以為空,也可以為string數組類,其作用是接受命令行參數,例如在命令行下運行程序時,args提供了輸入命令行參數的入口。
另外,值得一提的是,雖然CLR支持參數默認值,但是C#中卻不能設置參數默認值,這一點讓我很郁悶,不知為何?不過可以通過重載來變相實現,具體如下:
static void JudgeKind(string name, string kind)
{
Console.WriteLine("{0} is a {1}", name, kind);
}
static void JudgeKind(string name)
{
//偽代碼
if(name is person)
{
Console.WriteLine(name, "People");
}
}
這種方法可以擴展,可以實現更多個默認參數實現,不過,說實話有些多此一舉,不夠靈活,不爽不爽。
3.傳遞的基礎
接下來,我們接上面的示例討論,重點將參數傳遞的基礎做以交代,以便對參數之惑有一個從簡入繁的演化過程。我們以基本概念的形式來一一列出這些基本概念,先混個臉兒熟,關於形參、實參、參數默認值的概念就不多做交代,參數傳遞是本文的核心內容,將在後文以大量的筆墨來闡述。所以接下來的概念,我們就做以簡單的引入不花大量的精力來討論,主要包括:
3.1 泛型類型參數
泛型類型參數,可以是靜態的,例如MyGeneric<int>;也可以是動態的,此時它其實就是一個占位符,例如MyGeneric<T>中的T可以是任何類型的變量,在運行期動態替換為相應的類型參數。泛型類型參數一般也以T開頭來命名。
3.2 可變數目參數
一般來說參數個數都是固定的,定義為集群類型的參數可以實現可變數目參數的目的,但是.NET提供了更靈活的機制來實現可變數目參數,這就是使用param修飾符。可變數目參數的好處就是在某些情況下可以方便的提供對於參數個數不確定情況的實現,例如計算任意數字的加權和,連接任意字符串為一個字符串等。我們以一個簡單的示例來展開對這個問題的論述,為:
在此基礎上,我們將使用param關鍵字實現可變數目參數的規則和使用做以小結為:
param關鍵字的實質是:param是定制特性ParamArrayAttribute的縮寫(關於定制特性的詳細論述請參見第三回:歷史糾葛:特性和屬性),該特性用於指示編譯器的執行過程大概可以簡化為:編譯器檢查到方法調用時,首先調用不包含ParamArrayAttribute特性的方法,如果存在這種方法就施行調用,如果不存在才調用包含ParamArrayAttribute特性的方法,同時應用方法中的元素來填充一個數組,同時將該數組作為參數傳入調用的方法體。總之就是param就是提示編譯器實現對參數進行數組封裝,將可變數目的控制由編譯器來完成,我們可以很方便的從上述示例中得到啟示。例如:
static void ShowAgeSum(string team, params int[] ages){...}
實質上是這樣子:
static void ShowAgeSum(string team, [ParamArrayAttribute] int[] ages){...}
param修飾的參數必須為一維數組,事實上通常就是以群集方式來實現多個或者任意多個參數的控制的,所以數組是最簡單的選擇;
param修飾的參數數組,可是是任何類型。因此,如果需要接受任何類型的參數時,只要設置數組類型為object即可;
param必須在參數列表的最後一個,並且只能使用一次。
4.深入討論,傳遞的藝術
默認情況下,CRL中的方法都是按值傳遞的,但是在具體情況會根據傳遞的參數情況的不同而有不同的表現,我們在深入討論傳遞藝術的要求下,就是將不同的傳遞情況和不同的表現情況做以小結,從中剝離出參數傳遞復雜表現之內的實質所在。從而為開篇的幾個問題給出清晰的答案。
4.1 值類型參數的按值傳遞
首先,參數傳遞根據參數類型分為按值傳遞和按引用傳遞,默認情況下都是按值傳遞的。按值傳遞主要包括值類型參數的按值傳遞和引用類型參數的按值傳遞。值類型實例傳遞的是該值類型實例的一個拷貝,因此被調用方法操作的是屬於自己本身的實例拷貝,因此不影響原來調用方法中的實例值。以例為證:
// FileName : Anytao.net.My_Must_net
// Description : The .NET what you should know of arguments.
// Release : 2007/07/01 1.0
// Copyright : (C)2007 Anytao.com http://www.anytao.com
using System;
namespace Anytao.net.My_Must_net
{
class Args
{
public static void Main()
{
int a = 10;
Add(a);
Console.WriteLine(a);
}
private static void Add(int i)
{
i = i + 10;
Console.WriteLine(i);
}
}
}