C# 中 ref 与 out 的区别

      C#中,既可以通过值也可以通过引用传递参数。

    总的来说,通常我们向方法中传递的是值。方法获得的是这些值的一个拷贝,然后使用这些拷贝,当方法运行完毕后,这些拷贝将被丢弃,而原来的值不将受到影响。然而,通过引用传递参数允许函数成员更改参数的值,并保持该更改。

   当我们需要改变原来变量中的值,这时,我们可以向方法传递变量的引用,而不是变量的值。引用是一个变量,他可以访问原来变量的值,修改引用将修改原来变量的值。变量的值存储在内存中,可以创建一个引用,他指向变量在内存中的位置。当引用被修改时,修改的是内存中的值,因此变量的值可以将被修改。当我们调用一个含有引用参数的方法时,引用参数的修改对应着该引用所指向内存值的更改,因此,我们会明白,为什么当修改参数变量时,也将导致原来变量的值的改变。

        实际上从名称上我们可以清楚地看出两者的含义——传值参数传递的是调用参数的一份拷贝,而传址参数传递的是调用参数的内存地址,该参数在方法内外指向的是同一个存储位置

    若要通过引用传递参数,可使用ref或out关键字。ref和out这两个关键字都能够提供相似的功效,其作用也很像C中的指针变量。下面主要介绍这两个关键字的区别。   

    注:在C#中,方法的参数传递有四种类型:传值(by value),传址(by reference),输出参数(by output),数组参数(by array)。传值参数无需额外的修饰符,传址参数需要修饰符ref,输出参数需要修饰符out,数组参数需要修饰符params

MSDN中的定义:

        ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字

       out 关键字会导致参数通过引用来传递。这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化。若要使用 out 参数,方法定义和调用方法都必须显式使用 out 关键字。

首先,我们来看一个简单的例子:

  static void TestRefAndOut()
  {
     string s1 = "Good Luck!";//变量必须在传递之前进行初始化
     TestRef(ref s1);
     Console.WriteLine(s1);//output: Hello World!
  }
 static void TestRef(ref string str)
  {
     str = "Hello World!";
  }

      在TestRefAndOut()中,将字符串s1以ref关键字的方式传到方法TestRef(ref string str)中,在这个方法中,我们改变了s1的引用变量str的值,最后,回到TestRefAndOut()方法后输出s1的值,发现其值已被改变。

将上例中的ref换成out,代码如下:

 static void TestRefAndOut()
  {
     string s1 = "Good Luck!";
     //TestRef(ref s1);
     TestOut(out s1);
     Console.WriteLine(s1);//output: Hello World!
  }
  static void TestOut(out string str)
  {
     str = "Hello World!";//在方法进入后,必须赋值
  }    


        同样,在将ref换成out后,会发现最后的输出仍然是相同的,那这两个关键字的区别是什么呢?

进一步测试:
 ref:
  static void TestRefAndOut()
  {
     string s1 = "Good Luck!";//变量必须在传递之前进行初始化
     TestRef(ref s1);
  }

  static void TestRef(ref string str)
  {
     Console.WriteLine(str);//output: Good Lick!            
  }
  out:
  static void TestRefAndOut()
  {
      string s1 = "Good Luck!";
      TestOut(out s1);
  }

  static void TestOut(out string str)
  {
     Console.WriteLine(str);//compile does not pass
  }

        ref的那段代码顺利编译,输出"Good Luck!",而out那段代码却无法通过编译,提示“Use of unassigned out parameter 'str' ”,即使用了未分配地址的out参数str。怎么回事呢?原来 out参数在进入方法的时候,C#会自动清空它的一切引用和指向,所以在上面的out例子中, 必需先要为str参数赋值。如以下程序。
     
  static void TestRefAndOut()
  {
     string s1 = "Good Luck!";
     TestOut(out s1);
  }
  static void TestOut(out string str)
  {
     str = "Hello World!";//因为原来str的引用和指向已经清空,所以,这里必需先要为str参数赋值
     Console.WriteLine(str);//output: Hello World!
  }		
		

         

        Ok,得到第一个区别: out 参数在进入方法(函数)后清空自己,使自己变成一个干净的参数,因此,在方法返回之前或再使用out参数前,必须为 out 参数赋值只有地址没有值的参数是不能被.net接受的);而在被调用方法中,ref参数是不必在使用前先赋值的,甚至也可以被调用方法中不改变ref参数的值,这都不会引起编译错误。

再继续看一段代码:
  ref:
  static void TestRefAndOut()
  {
     string s1;
     TestRef(ref s1);//没有为s1赋值,即开始的引用为空
     Console.WriteLine(s1);//compile does not pass!
  }
  static void TestRef(ref string str)
  {
     str = Hello World!";//在方法在为str赋值,无效
  }    
  out:
  static void TestRefAndOut()
  {
     string s1;
     TestOut(out s1);//同样没有为s1赋值
     Console.WriteLine(s1);//output: Hello World!
  }
  static void TestOut(out string str)
  {
     str = "Hello World!";//在方法中为str赋值,有效
  }   	

        这回发现,ref这段代码无法编译了,s1是一个空引用,所以无法使用。而out参数则因为上述的那个原因,它不在乎s1是不是空引用,因为就算s1不是空引用,它也会把s1变成空引用的。

        Ok,第二个区别ref参数在使用前必需初始化,而out不需要。嗯,由上边两个区别可以引申一下,out参数只出不进,ref参数有进有出。

        在用法上:out适合用在需要retrun多个返回值的地方,而ref则用在,需要被调用的方法( TestRef(ref string str)),修改调用者(TestRefAndOut())的引用的时候。下面用两个实例说明。

ref有进有出,out只进不出示例代码:

public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }
        //ref是有进有出,而out是只出不进,
        public int RefValue(int i, ref int j)
        {
            int k = j; //表现为有进,将j在外部的初始值传进来
            j = 222;   //表现为有出,j是返回值
            return i + k;
        }

        public int OutValue(int i, out int j)
        {
            j = 222;//表现为有出,在方法内部赋值,并作为out的返回值输出
            return i + j;
        }


        private void btn_ref_Click(object sender, EventArgs e)
        {
            int m = 0;
            MessageBox.Show(RefValue(1, ref m).ToString());//返回值为1
            MessageBox.Show(m.ToString());//返回值是222,说明refvalue的调用ref是传址
        }

        private void btn_out_Click(object sender, EventArgs e)
        {
            int m;
            MessageBox.Show(OutValue(1, out m).ToString());//返回223
            MessageBox.Show(m.ToString());//返回222
        }
    }

out适用于多个返回值示例:

 class gump
    {     
        public void math_routines(double x,out double half,out double squared,out double cubed)
        {
          half=x/2;    //输出的值需要靠x赋值,x不能再为输出值
          squared=x*x;//x为赋值,所以可以是double 类型的变量,也可以是ref 标识的变量,但是不能是out输出类型的
          cubed=x*x*x;
        }
    }
    class TestApp
    {
        public static void calc()
        {
            gump doit=new gump();


            double x1=600;
            double half1=0;
            double squared1=0;
            double cubed1=0;
   
            /*
             double x1=600;
             double half1;
             double squared1;
             double cubed1;
            */


            Console.WriteLine("Before method->x1={0}",x1);//600
            Console.WriteLine("half1={0}",half1);    //0
            Console.WriteLine("squared1={0}", squared1); //0
            Console.WriteLine("cubed1={0}", cubed1); //0


            doit.math_routines(x1, out half1, out squared1, out cubed1);//该函数调用前后,half1,squared1,cubed1的值发生了变化
    
            Console.WriteLine("After method->x1={0}",x1);//600
            Console.WriteLine("half1={0}",half1); //300
            Console.WriteLine("squared1={0}",squared1);//3600
            Console.WriteLine("cubed1={0}",cubed1);//2160000
        }
    }

        我们发现,ref和out似乎可以实现相同的功能。因为都可以改变传递到方法中的变量的值但是,二者本质本质的区别就是,ref即可以是传入值又可以是传出值,out只是传出值。在含有out关键字的方法中,变量必须由方法参数中不含out(可以是ref)的变量赋值或者由全局(即方法可以使用的该方法外部变量)变量赋值,out的宗旨是保证每一个传出变量都必须被赋值

        上面代码中被/**/注释掉的部分,可以直接使用。也就是说,在调用方法前可以不初始化变量.但是\"x1=600\"是要赋值的,否则要报错.而ref参数,在传递给方法时,就已经是还有值的了,所以ref侧重修改,out侧重输出。

总结:

      1、使用ref型参数时,传入的参数必须先被初始化。对out而言,必须在方法中对其完成初始化。

      2、使用refout时,在方法的定义和执行时,都要加RefOut关键字。以满足匹配。

      3out适合用在需要retrun多个返回值的地方,而ref则用在需要被调用的方法修改调用者的引用时。

   4、ref有进有出,out只进不出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值