ref和out,以及一般方法的引用參數和值參數尋解


——老趙微博

 對與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的:

View Code
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的地址操作,當然也就能將參數的變化傳遞到方法外。

為了效果明顯,可以使用一個交換兩個數的方法來展示:

View Code
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的值。這是我的理解,還請路過的朋友多提自己的看法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM