1.1 類和對象
類 (class) 是最基礎的 C# 類型。類是一個數據結構,將狀態(字段)和操作(方法和其他函數成員)組合在一個單元中。類爲動態創建的類實例 (instance) 提供了定義,實例也稱爲對象 (object)。類支持繼承 (inheritance) 和多態性 (polymorphism),這是派生類 (derived class) 可用來擴展和專用化基類 (base class) 的機制。
使用類聲明可以創建新的類。類聲明以一個聲明頭開始,其組成方式如下:先指定類的屬性和修飾符,然後是類的名稱,接着是基類(如有)以及該類實現的接口。聲明頭後面跟着類體,它由一組位於一對大括號 { 和 } 之間的成員聲明組成。
下面是一個名爲 Point 的簡單類的聲明:
public class Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
類的實例使用 new 運算符創建,該運算符爲新的實例分配內存,調用構造函數初始化該實例,並返回對該實例的引用。下面的語句創建兩個 Point 對象,並將對這兩個對象的引用存儲在兩個變量中:
Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);
當不再使用對象時,該對象佔用的內存將自動收回。在 C# 中,沒有必要也不可能顯式釋放分配給對象的內存。
1.1.1 成員
類的成員或者是靜態成員 (static member),或者是實例成員 (instance member)。靜態成員屬於類,實例成員屬於對象(類的實例)。
下表提供了類所能包含的成員種類的概述。
成員 |
說明 |
常量 |
與類關聯的常數值 |
字段 |
類的變量 |
方法 |
類可執行的計算和操作 |
屬性 |
與讀寫類的命名屬性相關聯的操作 |
索引器 |
與以數組方式索引類的實例相關聯的操作 |
事件 |
可由類生成的通知 |
運算符 |
類所支持的轉換和表達式運算符 |
構造函數 |
初始化類的實例或類本身所需的操作 |
析構函數 |
在永久丟棄類的實例之前執行的操作 |
類型 |
類所聲明的嵌套類型 |
1.1.2 可訪問性
類的每個成員都有關聯的可訪問性,它控制能夠訪問該成員的程序文本區域。有五種可能的可訪問性形式。下表概述了這些可訪問性。
可訪問性 |
含義 |
public |
訪問不受限制 |
protected |
訪問僅限於此類和從此類派生的類 |
internal |
訪問僅限於此程序 |
protected internal |
訪問僅限於此程序和從此類派生的類 |
private |
訪問僅限於此類 |
1.1.3 基類
類聲明可通過在類名稱後面加上一個冒號和基類的名稱來指定一個基類。省略基類的指定等同於從類型 object 派生。在下面的示例中,Point3D 的基類爲 Point,而 Point 的基類爲 object:
public class Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Point3D: Point
{
public int z;
public Point3D(int x, int y, int z): Point(x, y) {
this.z = z;
}
}
一個類繼承它的基類的成員。繼承意味着一個類隱式地包含其基類的所有成員,但基類的構造函數除外。派生類能夠在繼承基類的基礎上添加新的成員,但是它不能移除繼承成員的定義。在前面的示例中,Point3D 類從 Point 類繼承了 x 字段和 y 字段,每個 Point3D 實例都包含三個字段 x、y 和 z。
從某個類類型到它的任何基類類型存在隱式的轉換。因此,類類型的變量可以引用該類的實例或任何派生類的實例。例如,對於前面給定的類聲明,Point 類型的變量既可以引用 Point 也可以引用 Point3D:
Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);
1.1.4 字段
字段是與類或類的實例關聯的變量。
使用 static 修飾符聲明的字段定義了一個靜態字段 (static field)。一個靜態字段只標識一個存儲位置。對一個類無論創建了多少個實例,它的靜態字段永遠都只有一個副本。
不使用 static 修飾符聲明的字段定義了一個實例字段 (instance field)。類的每個實例都包含了該類的所有實例字段的一個單獨副本。
在下面的示例中,Color 類的每個實例都有實例字段 r、g 和 b 的單獨副本,但是 Black、White、Red、Green 和 Blue 靜態字段只存在一個副本:
public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);
private byte r, g, b;
public Color(byte r, byte g, byte b) {
this.r = r;
this.g = g;
this.b = b;
}
}
如上面的示例所示,可以使用 readonly 修飾符聲明只讀字段 (read-only field)。給 readonly 字段的賦值只能作爲字段聲明的組成部分出現,或在同一類中的實例構造函數或靜態構造函數中出現。
1.1.5 方法
方法 (method) 是一種用於實現可以由對象或類執行的計算或操作的成員。靜態方法 (static method) 通過類來訪問。實例方法 (instance method) 通過類的實例來訪問。
方法具有一個參數 (parameter) 列表(可能爲空),表示傳遞給該方法的值或變量引用;方法還具有一個返回類型 (return type),指定該方法計算和返回的值的類型。如果方法不返回值,則其返回類型爲 void。
方法的簽名 (signature) 在聲明該方法的類中必須唯一。方法的簽名由方法的名稱及其參數的數目、修飾符和類型組成。方法的簽名不包含返回類型。
1.1.5.1 參數
參數用於向方法傳遞值或變量引用。方法的參數從方法被調用時指定的實參 (argument) 獲取它們的實際值。有四種類型的參數:值參數、引用參數、輸出參數和參數數組。
值參數 (value parameter) 用於輸入參數的傳遞。一個值參數相當於一個局部變量,只是它的初始值來自爲該形參傳遞的實參。對值參數的修改不影響爲該形參傳遞的實參。
引用參數 (reference parameter) 用於輸入和輸出參數傳遞。爲引用參數傳遞的實參必須是變量,並且在方法執行期間,引用參數與實參變量表示同一存儲位置。引用參數使用 ref 修飾符聲明。下面的示例演示 ref 參數的使用。
using System;
class Test
{
static void Swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}
static void Main() {
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine("{0} {1}", i, j); // Outputs "2 1"
}
}
輸出參數 (output parameter) 用於輸出參數的傳遞。對於輸出參數來說,調用方提供的實參的初始值並不重要,除此之外,輸出參數與引用參數類似。輸出參數是用 out 修飾符聲明的。下面的示例演示 out 參數的使用。
using System;
class Test
{
static void Divide(int x, int y, out int result, out int remainder) {
result = x / y;
remainder = x % y;
}
static void Main() {
int res, rem;
Divide(10, 3, out res, out rem);
Console.WriteLine("{0} {1}", res, rem); // Outputs "3 1"
}
}
參數數組 (parameter array) 允許向方法傳遞可變數量的實參。參數數組使用 params 修飾符聲明。只有方法的最後一個參數纔可以是參數數組,並且參數數組的類型必須是一維數組類型。System.Console 類的 Write 和 WriteLine 方法就是參數數組用法的很好示例。它們的聲明如下。
public class Console
{
public static void Write(string fmt, params object[] args) {...}
public static void WriteLine(string fmt, params object[] args) {...}
...
}
在使用參數數組的方法中,參數數組的行爲完全就像常規的數組類型參數。但是,在具有參數數組的方法的調用中,既可以傳遞參數數組類型的單個實參,也可以傳遞參數數組的元素類型的任意數目的實參。在後一種情況下,將自動創建一個數組實例,並使用給定的實參對它進行初始化。示例:
Console.WriteLine("x={0} y={1} z={2}", x, y, z);
等價於寫下面的語句。
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine("x={0} y={1} z={2}", args);
1.1.5.2 方法體和局部變量
方法體指定了在該方法被調用時將執行的語句。
方法體可以聲明僅用在該方法調用中的變量。這樣的變量稱爲局部變量 (local variable)。局部變量聲明指定了類型名稱、變量名稱,還可指定初始值。下面的示例聲明一個初始值爲零的局部變量 i 和一個沒有初始值的變量 j。
using System;
class Squares
{
static void Main() {
int i = 0;
int j;
while (i < 10) {
j = i * i;
Console.WriteLine("{0} x {0} = {1}", i, j);
i = i + 1;
}
}
}
C# 要求在對局部變量明確賦值 (definitely assigned) 之後才能獲取其值。例如,如果前面的 i 的聲明未包括初始值,則編譯器將對隨後對 i 的使用報告錯誤,因爲 i 在程序中的該位置還沒有明確賦值。
方法可以使用 return 語句將控制返回到它的調用方。在返回 void 的方法中,return 語句不能指定表達式。在返回非 void 的方法中,return 語句必須含有一個計算返回值的表達式。
1.1.5.3 靜態方法和實例方法
使用 static 修飾符聲明的方法爲靜態方法 (static method)。靜態方法不對特定實例進行操作,並且只能訪問靜態成員。
不使用 static 修飾符聲明的方法爲實例方法 (instance method)。實例方法對特定實例進行操作,並且能夠訪問靜態成員和實例成員。在調用實例方法的實例上,可以通過 this 顯式地訪問該實例。而在靜態方法中引用 this 是錯誤的。
下面的 Entity 類具有靜態成員和實例成員。
class Entity
{
static int nextSerialNo;
int serialNo;
public Entity() {
serialNo = nextSerialNo++;
}
public int GetSerialNo() {
return serialNo;
}
public static int GetNextSerialNo() {
return nextSerialNo;
}
public static void SetNextSerialNo(int value) {
nextSerialNo = value;
}
}
每個 Entity 實例都包含一個序號(並且假定這裏省略了一些其他信息)。Entity 構造函數(類似於實例方法)使用下一個可用的序號初始化新的實例。由於該構造函數是一個實例成員,它既可以訪問 serialNo 實例字段,也可以訪問 nextSerialNo 靜態字段。
GetNextSerialNo 和 SetNextSerialNo 靜態方法可以訪問 nextSerialNo 靜態字段,但是如果訪問 serialNo 實例字段就會產生錯誤。
下面的示例演示 Entity 類的使用。
using System;
class Test
{
static void Main() {
Entity.SetNextSerialNo(1000);
Entity e1 = new Entity();
Entity e2 = new Entity();
Console.WriteLine(e1.GetSerialNo()); // Outputs "1000"
Console.WriteLine(e2.GetSerialNo()); // Outputs "1001"
Console.WriteLine(Entity.GetNextSerialNo()); // Outputs "1002"
}
}
注意:SetNextSerialNo 和 GetNextSerialNo 靜態方法是在類上調用的,而 GetSerialNo 實例方法是在該類的實例上調用的。
1.1.5.4 虛方法、重寫方法和抽象方法
若一個實例方法的聲明中含有 virtual 修飾符,則稱該方法爲虛方法 (virtual method)。若其中沒有 virtual 修飾符,則稱該方法爲非虛方法 (non-virtual method)。
在調用一個虛方法時,該調用所涉及的那個實例的運行時類型 (runtime type) 確定了要被調用的究竟是該方法的哪一個實現。在非虛方法調用中,實例的編譯時類型 (compile-time type) 是決定性因素。
虛方法可以在派生類中重寫 (override)。當某個實例方法聲明包括 override 修飾符時,該方法將重寫所繼承的具有相同簽名的虛方法。虛方法聲明用於引入新方法,而重寫方法聲明則用於使現有的繼承虛方法專用化(通過提供該方法的新實現)。
抽象 (abstract) 方法是沒有實現的虛方法。抽象方法使用 abstract 修飾符進行聲明,並且只有在同樣被聲明爲 abstract 的類中才允許出現。抽象方法必須在每個非抽象派生類中重寫。
下面的示例聲明一個抽象類 Expression,它表示一個表達式樹節點;它有三個派生類 Constant、VariableReference 和 Operation,它們分別實現了常量、變量引用和算術運算的表達式樹節點。
using System;
using System.Collections;
public abstract class Expression
{
public abstract double Evaluate(Hashtable vars);
}
public class Constant: Expression
{
double value;
public Constant(double value) {
this.value = value;
}
public override double Evaluate(Hashtable vars) {
return value;
}
}
public class VariableReference: Expression
{
string name;
public VariableReference(string name) {
this.name = name;
}
public override double Evaluate(Hashtable vars) {
object value = vars[name];
if (value == null) {
throw new Exception("Unknown variable: " + name);
}
return Convert.ToDouble(value);
}
}
public class Operation: Expression
{
Expression left;
char op;
Expression right;
public Operation(Expression left, char op, Expression right) {
this.left = left;
this.op = op;
this.right = right;
}
public override double Evaluate(Hashtable vars) {
double x = left.Evaluate(vars);
double y = right.Evaluate(vars);
switch (op) {
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;
}
throw new Exception("Unknown operator");
}
}
上面的四個類可用於爲算術表達式建模。例如,使用這些類的實例,表達式 x + 3 可如下表示。
Expression e = new Operation(
new VariableReference("x"),
'+',
new Constant(3));
Expression 實例的 Evaluate 方法將被調用,以計算給定的表達式的值,從而產生一個 double 值。該方法接受一個包含變量名稱(作爲哈希表項的鍵)和值(作爲項的值)的 Hashtable 作爲參數。Evaluate 方法是一個虛抽象方法,意味着非抽象派生類必須重寫該方法以提供具體的實現。
Constant 的 Evaluate 實現只是返回所存儲的常量。VariableReference 的實現在哈希表中查找變量名稱,並返回產生的值。Operation 的實現先對左操作數和右操作數求值(通過遞歸調用它們的 Evaluate 方法),然後執行給定的算術運算。
下面的程序使用 Expression 類,對於不同的 x 和 y 值,計算表達式 x * (y + 2) 的值。
using System;
using System.Collections;
class Test
{
static void Main() {
Expression e = new Operation(
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);
Hashtable vars = new Hashtable();
vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // Outputs "21"
vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // Outputs "16.5"
}
}
1.1.5.5 方法重載
方法重載 (overloading) 允許同一類中的多個方法具有相同名稱,條件是這些方法具有唯一的簽名。在編譯一個重載方法的調用時,編譯器使用重載決策 (overload resolution) 確定要調用的特定方法。重載決策將查找與參數最佳匹配的方法,如果沒有找到任何最佳匹配的方法則報告錯誤信息。下面的示例演示重載決策的工作機制。Main 方法中的每個調用的註釋表明實際被調用的方法。
class Test
{
static void F() {...}
Console.WriteLine("F()");
}
static void F(object x) {
Console.WriteLine("F(object)");
}
static void F(int x) {
Console.WriteLine("F(int)");
}
static void F(double x) {
Console.WriteLine("F(double)");
}
static void F(double x, double y) {
Console.WriteLine("F(double, double)");
}
static void Main() {
F(); // Invokes F()
F(1); // Invokes F(int)
F(1.0); // Invokes F(double)
F("abc"); // Invokes F(object)
F((double)1); // Invokes F(double)
F((object)1); // Invokes F(object)
F(1, 1); // Invokes F(double, double)
}
}
正如該示例所示,總是通過顯式地將實參強制轉換爲確切的形參類型,來選擇一個特定的方法。
1.1.6 其他函數成員
包含可執行代碼的成員統稱爲類的函數成員 (function member)。前一節描述的方法是函數成員的主要類型。本節描述 C# 支持的其他種類的函數成員:構造函數、屬性、索引器、事件、運算符和析構函數。
下表演示一個名爲 List 的類,它實現一個可增長的對象列表。該類包含了幾種最常見的函數成員的示例。
public class List |
|
const int defaultCapacity = 4; |
常量 |
object[] items; |
字段 |
public List(): List(defaultCapacity) {} public List(int capacity) { |
構造函數 |
public int Count { public string Capacity { |
屬性 |
public object this[int index] { |
索引器 |
public void Add(object item) { protected virtual void OnChanged() { public override bool Equals(object other) { static bool Equals(List a, List b) { |
方法 |
public event EventHandler Changed; |
事件 |
public static bool operator ==(List a, List b) { public static bool operator !=(List a, List b) { |
運算符 |
} |
1.1.6.1 構造函數
C# 支持兩種構造函數:實例構造函數和靜態構造函數。實例構造函數 (instance constructor) 是實現初始化類實例所需操作的成員。靜態構造函數 (static constructor) 是一種用於在第一次加載類本身時實現其初始化所需操作的成員。
構造函數的聲明如同方法一樣,不過它沒有返回類型,並且它的名稱與其所屬的類的名稱相同。如果構造函數聲明包含 static 修飾符,則它聲明瞭一個靜態構造函數。否則,它聲明的是一個實例構造函數。
實例構造函數可以被重載。例如,List 類聲明瞭兩個實例構造函數,一個無參數,另一個接受一個 int 參數。實例構造函數使用 new 運算符進行調用。下面的語句分別使用 List 類的每個構造函數分配兩個 List 實例。
List list1 = new List();
List list2 = new List(10);
實例構造函數不同於其他成員,它是不能被繼承的。一個類除了其中實際聲明的實例構造函數外,沒有其他的實例構造函數。如果沒有爲某個類提供任何實例構造函數,則將自動提供一個不帶參數的空的實例構造函數。
1.1.6.2 屬性
屬性 (propery) 是字段的自然擴展。屬性和字段都是命名的成員,都具有相關的類型,且用於訪問字段和屬性的語法也相同。然而,與字段不同,屬性不表示存儲位置。相反,屬性有訪問器 (accessor),這些訪問器指定在它們的值被讀取或寫入時需執行的語句。
屬性的聲明與字段類似,不同的是屬性聲明以位於定界符 { 和 } 之間的一個 get 訪問器和/或一個 set 訪問器結束,而不是以分號結束。同時具有 get 訪問器和 set 訪問器的屬性是讀寫屬性 (read-write property),只有 get 訪問器的屬性是隻讀屬性 (read-only property),只有 set 訪問器的屬性是隻寫屬性 (write-only property)。
get 訪問器相當於一個具有屬性類型返回值的無參數方法。除了作爲賦值的目標,當在表達式中引用屬性時,將調用該屬性的 get 訪問器以計算該屬性的值。
set 訪問器相當於具有一個名爲 value 的參數並且沒有返回類型的方法。當某個屬性作爲賦值的目標被引用,或者作爲 ++ 或 -- 的操作數被引用時,將調用 set 訪問器,並傳入提供新值的實參。
List 類聲明瞭兩個屬性 Count 和 Capacity,它們分別是隻讀屬性和讀寫屬性。下面是這些屬性的使用示例。
List names = new List();
names.Capacity = 100; // Invokes set accessor
int i = names.Count; // Invokes get accessor
int j = names.Capacity; // Invokes get accessor
與字段和方法相似,C# 同時支持實例屬性和靜態屬性。靜態屬性使用 static 修飾符聲明,而實例屬性的聲明不帶該修飾符。
屬性的訪問器可以是虛的。當屬性聲明包括 virtual、abstract 或 override 修飾符時,修飾符應用於該屬性的訪問器。
1.1.6.3 索引器
索引器 (indexer) 是這樣一個成員:它使對象能夠用與數組相同的方式進行索引。索引器的聲明與屬性類似,不同的是該成員的名稱是 this,後跟一個位於定界符 [ 和 ] 之間的參數列表。在索引器的訪問器中可以使用這些參數。與屬性類似,索引器可以是讀寫、只讀和只寫的,並且索引器的訪問器可以是虛的。
該 List 類聲明瞭單個讀寫索引器,該索引器接受一個 int 參數。該索引器使得通過 int 值對 List 實例進行索引成爲可能。例如
List numbers = new List();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++) {
string s = (string)names[i];
names[i] = s.ToUpper();
}
索引器可以被重載,這意味着一個類可以聲明多個索引器,只要它們的參數的數量和類型不同即可。
1.1.6.4 事件
事件 (event) 是一種使類或對象能夠提供通知的成員。事件的聲明與字段類似,不同的是事件的聲明包含 event 關鍵字,並且類型必須是委託類型。
在聲明事件成員的類中,事件的行爲就像委託類型的字段(前提是該事件不是抽象的並且未聲明訪問器)。該字段存儲對一個委託的引用,該委託表示已添加到該事件的事件處理程序。如果尚未添加事件處理程序,則該字段爲 null。
List 類聲明瞭一個名爲 Changed 的事件成員,它指示有一個新的項已被添加到列表中。Changed 事件由 OnChanged 虛方法引發,後者先檢查該事件是否爲 null(表明沒有處理程序)。“引發一個事件”與“調用一個由該事件表示的委託”完全等效,因此沒有用於引發事件的特殊語言構造。
客戶端通過事件處理程序 (event handler) 來響應事件。事件處理程序使用 += 運算符添加,使用 -= 運算符移除。下面的示例向 List 類的 Changed 事件添加一個事件處理程序。
using System;
class Test
{
static int changeCount;
static void ListChanged(object sender, EventArgs e) {
changeCount++;
}
static void Main() {
List names = new List();
names.Changed += new EventHandler(ListChanged);
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
Console.WriteLine(changeCount); // Outputs "3"
}
}
對於要求控制事件的底層存儲的高級情形,事件聲明可以顯式提供 add 和 remove 訪問器,它們在某種程度上類似於屬性的 set 訪問器。
1.1.6.5 運算符
運算符 (operator) 是一種類成員,它定義了可應用於類實例的特定表達式運算符的含義。可以定義三種運算符:一元運算符、二元運算符和轉換運算符。所有運算符都必須聲明爲 public 和 static。
List 類聲明瞭兩個運算符 operator == 和 operator !=,從而爲將那些運算符應用於 List 實例的表達式賦予了新的含義。具體而言,上述運算符將兩個 List 實例的相等關係定義爲逐一比較其中所包含的對象(使用所包含對象的 Equals 方法)。下面的示例使用 == 運算符比較兩個 List 實例。
using System;
class Test
{
static void Main() {
List a = new List();
a.Add(1);
a.Add(2);
List b = new List();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b); // Outputs "True"
b.Add(3);
Console.WriteLine(a == b); // Outputs "False"
}
}
第一個 Console.WriteLine 輸出 True,原因是兩個列表包含的對象數目和值均相同。如果 List 未定義 operator ==,則第一個 Console.WriteLine 將輸出 False,原因是 a 和 b 引用的是不同的 List 實例。
1.1.6.6 析構函數
析構函數 (destructor) 是一種用於實現銷燬類實例所需操作的成員。析構函數不能帶參數,不能具有可訪問性修飾符,也不能被顯式調用。垃圾回收期間會自動調用所涉及實例的析構函數。
垃圾回收器在決定何時回收對象和運行析構函數方面允許有廣泛的自由度。具體而言,析構函數調用的時機並不是確定的,析構函數可能在任何線程上執行。由於這些以及其他原因,僅當沒有其他可行的解決方案時,才應在類中實現析構函數。
c#引用參數傳遞的深入剖析
在c#中,數據有2中基本類型:值類型和引用類型
值類型的變量存儲數據,而引用類型的變量存儲對實際數據的引用。
在參數傳遞時,值類型是以值的形式傳遞的,是將要傳遞的參數的值複製給函數的形參,因此在函數體類對於該形參的任何改變都不會影響原來的值;
引用類型是以對象引用的形式傳遞的,是將要傳遞的對象的引用複製給函數的形參,這時形參是實參引用的複製,注意:是引用的複製,而不是原引用,和原引用指向相同的對象,因此對於引用對象所做的更改將會直接影響原來的值,但是對於引用本身,在函數內的任何改變將不會影響原引用。
給一個直觀的示例:
class A { public string data=""; } class Program { static void F( A a1) { //a1指向傳來的對象a a1.data = "2";//修改a1指向的對象 a1 = new A();//a1指向另一個對象,注意,這時a1已經不指向原來的對象a了,而原來的引用還是指向對象a a1.data = "3";//修改新建的對象,不會影響原來對象a的值 } static void Main() { A a = new A();//實例化A的一個對象,並用a1指向該對象 a.data = "1";//將a的data字段賦值爲"1" F(a);//調用函數F,注意:這時將對象a的引用(不是對象a)賦值給參數a1, Console.WriteLine(a.data); } }結果是2而不是3也不是1.ref 串參數:ref 關鍵字使參數按引用傳遞。其效果是,當控制權傳遞迴調用方法時,在方法中對參數所做的任何更改都將反映在該變量中。若要使用 ref 參數,則方法定義和調用方法都必須顯式使用 ref 關鍵字。例如:對於值類型,可以向上面的引用串參數一樣傳遞,對於已經是引用類型的參數,大家可能會說那不是多此一舉嗎?其實不然,因爲其中的實機理完全不一樣:考查上個示例的變種 class A { public string data=""; } class Program { static void F( ref A a1) { //a1和a是同一個實例,而不是指向同一對象的引用,即a1和a在存在於內存中的地址是一樣的 a1.data = "2";//修改a1指向的對象 a1 = new A();//a1指向另一個對象,理所當然別名a也指向該對象了,注意,這時原來的對象已經沒有任何引用指向了,因此,可以說原來的對象已經不可訪問了。 a1.data = "3";//修改新建的對象的屬性 } static void Main() { A a = new A();//實例化A的一個對象,並用a1指向該對象 a.data = "1";//將a的data字段賦值爲"1" F(ref a);//調用函數F,注意:這時將對象a的引用傳給a1,不是賦值,相當與 給a對象的引用起了個別名 Console.WriteLine(a.data);//這時a已經指向函數中新建的對象,因此值應爲"3" } } 可以這麼理解,沒有ref時的引用對象的參數傳遞就相當於c++中的一般指針傳遞(函數聲明相當於: void F(Type * v)),而有ref時的引用對象的參數傳遞相當於c++中的一般指向指針的指針傳遞(函數聲明相當於: void F(Type ** v)).