——老趙微博
對與ref和out的區別,我相信很多人都知道,這里我簡單羅列下:
1.首先ref和out兩種類型的參數都是可以將方法內對參數的修改傳遞到方法外面
2.ref參數需要在傳遞之前初始化,out不需要,out參數在返回時必須賦值
3.在CLR角度看ref和out沒什么區別,但是C#編譯器采取不同的方式對待
下面通過一些實例來進一步了解
①一般方法傳參
namespace ConsoleApplication1 { class Program { static void Main(string[] args) { TestParas tp = new TestParas(); Int32 a = 300; String b = "wan"; String ret = tp.Add(a, b); Console.WriteLine(ret); } } public class TestParas { public String Add(Int32 a, String b) { return a.ToString() + b; } } }
上面是非常普通,常見的傳參。查看IL:
.method private hidebysig static void Main ( string[] args ) cil managed { .entrypoint .locals init ( [0] class ConsoleApplication1.TestParas tp, [1] int32 a, [2] string b, [3] string 'ret' ) IL_0000: nop
//下面兩行定義了一個tp的引用類型的實例,這里創建一個對象,並把對象的引用入棧 IL_0001: newobj instance void ConsoleApplication1.TestParas::.ctor() IL_0006: stloc.0 //這里會有一個出棧,把出棧的值存在本地變量里,也就是這里的tp IL_0007: ldc.i4 300 //這里定義一個值類型,先將提供的值入棧 IL_000c: stloc.1 //出棧,將值存在本地變量a中 IL_000d: ldstr "wan" //這里定義一個引用類型,將對該字符串的引用存入元素據 IL_0012: stloc.2 //取出當前棧的值放到本地變量b中 IL_0013: ldloc.0 IL_0014: ldloc.1 IL_0015: ldloc.2 IL_0016: callvirt instance string ConsoleApplication1.TestParas::Add(int32, string)//這里調用add方法,並傳入兩個參數 IL_001b: stloc.3 IL_001c: ldloc.3 IL_001d: call void [mscorlib]System.Console::WriteLine(string) IL_0022: nop IL_0023: ret }
接下來看看add方法的IL:
下面改變代碼,傳入帶ref和out的:

namespace ConsoleApplication1 { class Program { static void Main(string[] args) { TestParas tp = new TestParas(); Int32 a = 300; String b = "wan"; String ret = tp.Add(ref a, ref b); Console.WriteLine(ret); } } public class TestParas { public String Add(ref Int32 a, ref String b) { return a.ToString() + b; } } }
這時對應的IL也發生了變化,變化的部分如圖:
這里跟C語言里面的一種傳參方式相同,傳遞的是地址。接着看下Add方法的IL:
這里可以看到,在Add方法里定義了兩個地址,分別對應傳入的a,b的地址。所以在這里操作是針對a,b的地址操作,當然也就能將參數的變化傳遞到方法外。
為了效果明顯,可以使用一個交換兩個數的方法來展示:

namespace ConsoleApplication1 { class Program { static void Main(string[] args) { TestParas tp = new TestParas(); Int32 a = 300; Int32 c = 1000; Console.WriteLine("交換a:{0}和c:{1}",a,c); tp.Change2(a, c); Console.WriteLine("沒有加ref執行交換,a:{0},c{1}", a, c); tp.Change(ref a, ref c); Console.WriteLine("加上ref交換后a:{0},c:{1}",a,c); } } public class TestParas { public void Change(ref Int32 a, ref Int32 c) { Int32 tmp = 0; tmp = a; a = c; c = tmp; } public void Change2(Int32 a, Int32 c) { Int32 tmp = 0; tmp = a; a = c; c = tmp; } } }
運行結果為:
最后,給個結論吧:老趙的說法正確
ps:在默認情況下,CLR假定所有的方法參數傳遞的僅僅是值。當然除了加了ref和out以外。
今天看到有不少朋友在評論給了不少這方面的理解,還有一些擴展的例子,非常感謝,對我很有幫助。我覺得還可以挖掘一下,看下面的例子:
namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Person person = new Person(); person.Name = "初始值"; person.Age = 24; Int32 a = 24; ProcessPerson proc = new ProcessPerson(); Console.WriteLine("姓名:" + person.Name + " 年齡:" + person.Age + " a:" + a); proc.Change(person,ref a); Console.WriteLine("姓名:" + person.Name + " 年齡:" + person.Age + " a:" + a); } } public class Person { public String Name { get; set; } public Int32 Age { get; set; } } public class ProcessPerson { public void Change(Person obj,ref Int32 a) { obj.Name = "被修改"; obj.Age = 25; a = 25; } } }
首先說明下,這里Change方法的Person obj參數,加不加ref都會返回被修改的值。這里的obj是一個引用類型,里面包括Name,Age屬性分別是引用類型和值類型。這里對它們的修改是傳遞到了方法外的。而對應的值類型a參數,則會在加了ref后發送改變。
所以這里我推測:首先,CLR默認對所有參數傳遞是值傳遞,所以這里應該成立,obj是一個引用類型。這里傳遞的應該obj包含的值(這里的值包含String類型的Name和Int32類型Age)。如果對obj的Name和Age修改,則在返回后在Main方法里面,打印出來的是修改之后的Name和Age的值。這是我的理解,還請路過的朋友多提自己的看法。