首先應該認清楚在C#中只有兩種類型:
1、引用類型(任何稱為“類”的類型)
2、值類型(結構或枚舉)
先來認識一下引用類型和值類型的區別:
函數傳參之引用類型:
1、先來一個簡單的引用類型傳參的實例:
//使用了C#6.0的一個新特性:using static System.Console;
class Program
{
static void StartTest1(string test)
{
test = "test2";
WriteLine(test);//輸出:"test2"
}
static void StartTest2(string test)
{
test = null;
WriteLine(test);//輸出:(空白)
}
static void Main(string[] args)
{
string test = "test1";
WriteLine(test);//輸出:"test1"
StartTest1(test);
WriteLine(test);//輸出:"test1"
StartTest2(test);
WriteLine(test);//輸出:"test1"
}
}
輸出結果:
test1
test2
test1
test1
結果分析:
首先明白字符串(string)類型是引用類型,但改變了它的值之后,並沒有影響到函數外面那個實參的值,這可能與大家的常識有點相違背,因為我們都知道若是變量以"引用傳遞"的方式傳遞,那么調用的方法可以通過更改其參數值,來改變調用者的變量值,但這里有一點需要說明的是:"引用傳遞"不是等價於引用類型傳參,這是很多人的誤解的地方。其實在C#當中,引用類型和值類型默認都是以“傳值”的方式傳遞數值(引用)的(引用類型的值就是引用(類似索引或地址),而不是對象本身)。
請看下圖詳細分析:
2、再來一個略微復雜的引用類型傳參的實例:
class Program
{
static void StartTest1(StringBuilder test)
{
test.Append("test2");
WriteLine(test);//輸出:"test1test2"
}
static void StartTest2(StringBuilder test)
{
test = null;
WriteLine(test);//輸出:(空白)
}
static void Main(string[] args)
{
StringBuilder test = new StringBuilder();
test.Append("test1");
WriteLine(test);//輸出:"test1"
StartTest1(test);
WriteLine(test);//輸出:"test1test2"
StartTest2(test);
WriteLine(test);//輸出:"test1test2"
ReadKey();
}
}
輸出結果:
test1
test1test2
test1test2
test1test2
結果分析:
StringBuilder和string同樣是引用類型,那為什么最終的StringBuilder類型值改變了呢?其實這里要糾正一下,真正改變的不是StringBuilder類型值(也就是引用的值),而是引用指向的字符數組引用指向的對象值改變了。在StringBuilder類里面封裝了一個字符數組(最終的輸出的就是這個字符數組,而那些操作也是對這個字符數組進行操作)。
結合上面兩個實例,對於引用類型傳參,從這里可以得出一個小結論:
1、在函數里面,若直接改變的是引用的值(也就是地址),那么之后的操作都不會影響到函數外面的那個變量
2、在函數里面,若直接改變的是引用指向的對象(值類型)的值(甚至更深層次的對象的值),那么就會影響到函數外面的變量
所以區分清楚改變的是引用的值還是引用指向的對象(值類型)的值是關鍵。
3、再來一個綜合的引用類型傳參的實例:
class Program
{
class Test
{
public int index;//值類型
public StringBuilder builder;//引用類型
public string Result{
get{return $"{index}:{builder.ToString()}";}
}
}
static void StartTest(Test test)
{
test.index++;
test.builder.Append("test2");
WriteLine(test.Result);//輸出:"2:test1test2"
test.index = new int();
test.builder = new StringBuilder();
test.builder.Append("test3");
WriteLine(test.Result);//輸出:"0:test3"
}
static void Main(string[] args)
{
Test test = new Test {
index = 0,
builder = new StringBuilder()
};
test.index++;
test.builder.Append("test1");
WriteLine(test.Result);//輸出:"1:test1"
StartTest(test);
WriteLine(test.Result);//輸出:"0:test3"
}
}
輸出結果:
1:test1
2:test1test2
0:test3
0:test3
結果分析:
略
[若是能夠明白1和2中的分析,這個應該沒有問題的]
函數傳參之值類型:
簡單的值類型傳參這里就不演示了,來一個含有引用類型的值類型傳參實例(只是將上例中的struct改為了class,這樣好做對比):
class Program
{
struct Test
{
public int index;//值類型
public StringBuilder builder;//引用類型
public string Result{
get{return $"{index}:{builder.ToString()}";}
}
}
static void StartTest(Test test)
{
test.index++;
test.builder.Append("test2");
WriteLine(test.Result);//輸出:"2:test1test2"
test.index = new int();
test.builder = new StringBuilder();
test.builder.Append("test3");
WriteLine(test.Result);//輸出:"0:test3"
}
static void Main(string[] args)
{
Test test = new Test {
index = 0,
builder = new StringBuilder()
};
test.index++;
test.builder.Append("test1");
WriteLine(test.Result);//輸出:"1:test1"
StartTest(test);
WriteLine(test.Result);//輸出:"1:test1test2"
}
}
輸出結果:
1:test1
2:test1test2
0:test3
1:test1test2
結果分析:
首先應該明白,值類型以"傳值"方式傳遞時,是一種淺拷貝,所以對於引用類型,只是復制了引用的值,副本(形參)中的引用指向的對象還是同一個。其他的自己分析應該明白。
結論:
1、無論是引用類型還是值類型,永遠不會傳遞對象本身。涉及到一個引用類型時,要么以“引用傳遞”的方式(使用了ref或out關鍵字)傳遞變量,要么以“傳值”的方式傳遞參數值(引用)。所以,通常函數傳參(不論是引用類型還是值類型),都是以“傳值”的方式傳遞的,只是要明白引用類型的值是引用本身(相當於一個索引或地址,而這個索引或地址最終指向的才是對象本身)。
2、“引用方式”傳遞與“傳值”傳遞方式最大的區別就是“引用方式”要使用ref或out關鍵字修飾,所以以這個為標准去區分函數傳參的方式(而不是以類型是引用類型還是值類型)。
3、對於傳入函數的引用類型變量,最終會不會受到函數內部修改的影響,需要區分清楚函數內部改變的是引用的值還是引用指向的對象(值類型)的值。