C# - 函数参数的传递

C# - 函数参数的传递

原地址:http://www.cnblogs.com/DonLiang/archive/2008/02/16/1070717.html

近段时间,有几个刚刚开始学习C#语言的爱好者问我:C#中的函数,其参数的传递,按值传递和按引用传递有什么区别。针对这一问题,我简单写了个示例程序,用以讲解,希望我没有把他们绕晕。因为,常听别人说起:“你不说我还明白,你一说,我就糊涂了”。
    好,现在开始吧。
    我们知道,在C#中,类型有值类型(例如int)和引用类型(例如string)之分,传递参数有按值传递和按引用传递之分。这样,简单的组合一下,我们可以得到以下几种传递方式:(1)按值传递值类型。(2)按值传递引用类型。(3)按引用传递值类型。(4)按引用传递引用类型。一般来说,除非使用特定的关键字(ref和out)否则参数是按值传递的。也就是说,会传递一个副本。传递副本的一个好处是,可以避免误操作而影响了原始值。原因是在被调用的函数体内,操作的是副本的值,而不是原始值。当然,传递副本也是有副作用的,最为突出的应该是由于复制而产生的性能损耗,这点在大型的值类型身上尤为突出。那么C#的编译器的默认行为为什么不是使用按引用传递参数呢?呵呵,其实我没仔细深入思考过这个问题。我猜测,是因为安全因素吧,就是怕函数误操作了原始值。这点应该和C#的编译器要求显示使用关键字(ref和out)差不多,都是为了清楚地表达使用的意图,以避免误操作。使用ref等关键字,暗示函数调用者知道,在函数体内,也许存在修改原始值的语句,会改变参数的值(或者叫状态)。
    用个简单的示例演示一下。
    示例代码如下所示:

using  System;
  
  
namespace  DonLiang
  
{
      
class Sample
      
{
          
          
public static void foo(int x)
          
{
             x 
= 10;
         }

 
         
//*
         public static void foo(ref int x)
         
{
             x 
= 10;
         }

         
//*/
 
         
/**//**//**//*
         public static void foo(out int x)
 22        {
 23            x = 10;
 24        }
 25        //
*/

         
 
         
         
public class Point
        
{
             
private int m_x;
             
private int m_y;
 
             
public Point()
            
{
                m_x 
= 0;
                m_y 
= 0;
            }


            
public Point(int x, int y)
           
{
                m_x 
= x;
                m_y 
= y;
             }


            
public void Change(int x, int y)
            
{
               m_x 
= x;
               m_y 
= y;
            }


            
public override string ToString()
            
{
               
return string.Format("The Point is ({0},{1})", m_x.ToString(), m_y.ToString());
            }

        }

      

       
        
public static void foo(Point p)
        
{
            p.Change(
1010);
        }


        
public static void foo(ref Point p)
        
{
            p.Change(
100100);
       }


        
public static void other(Point p)
        
{
            Point tmp 
= new Point(1316);
           p 
= tmp;
        }


        
public static void other(ref Point p)
        
{
            Point tmp 
= new Point(138168);
            p 
= tmp;
        }

     

       
        
static void Main(string[] args)
        
{
            
int n = 5;

            
//call the foo(int x) method and check what happened.
           Console.WriteLine("before call foo(int x) the n = " + n.ToString());
            foo(n);
            Console.WriteLine(
"after call foo(int x) the n = " + n.ToString());

            Console.WriteLine(
"--------------------------------------------------------------");

           
//call the foo(ref int x) method and check what happened.
            Console.WriteLine("before call foo(ref int x) the n = " + n.ToString());
           foo(
ref n);
            
//foo(out n);
            Console.WriteLine("after call foo(ref int x) the n = " + n.ToString());
            Console.WriteLine(
"--------------------------------------------------------------");
            Point p 
= new Point(55);
           Point q 
= p;

           
//call the foo(Point p) method and check what happened.
            Console.WriteLine("before call foo(Point p) the p = " + p.ToString());
            foo(p);
           Console.WriteLine(
"after call foo(Point p) the p = " + p.ToString());
            Console.WriteLine(
"q = " + q.ToString());

            Console.WriteLine(
"--------------------------------------------------------------");

            
//call the foo(ref Point p) method and check what happened.
            Console.WriteLine("before call foo(ref Point p) the n = " + p.ToString());
            foo(
ref p);
            Console.WriteLine(
"after call foo(ref Point p) the n = " + p.ToString());
            Console.WriteLine(
"q = " + q.ToString());

         Console.WriteLine(
"--------------------------------------------------------------");

            
//call the other(Point p) method and check what happened.
           Console.WriteLine("before call other(Point p) the n = " + p.ToString());
           other(p);
           Console.WriteLine(
"after call other(Point p) the n = " + p.ToString());
           Console.WriteLine(
"q = " + q.ToString());

            Console.WriteLine(
"--------------------------------------------------------------");

           
//call the other(ref Point p) method and check what happened.
           Console.WriteLine("before call other(ref Point p) the n = " + p.ToString());
            other(
ref p);
           Console.WriteLine(
"after call other(ref Point p) the n = " + p.ToString());
        Console.WriteLine(
"q = " + q.ToString());

            Console.ReadLine();
        }

        
    }

}

C# - 函数参数的传递_第1张图片

按值传递引用类型 和 按引用传递引用类型
    之所以把这两个放在一起讲,是因为,如结果图所示,两种传递方式,都成功修改了值——这两个函数都分别调用了一个辅助修改的函数Change,去修改内部状态,即m_x,m_y的值,从5到10。呃,竟然都可以成功修改原始值,那么,为什么会存在两种方式呢?它们有什么区别吗?分别用在什么地方?为了说明他们的区别,我特意写了两个名为other的函数,在函数内new一个Point对象,并使从参数传递过来的引用这个新生成的Point对象。值得提醒的是,这个引用其定义在函数体外。其运行如上图我用方框框起来那个。
    可以很清楚地看到,通过值传递方式,可以改变其值,却不能改变其本身所引用的对象;而按引用传递方式可以。

    顺便提一下,代码中,有一段注释掉的代码,使用out关键字的。当你尝试将其两者一起写着,然后,编译,C#编译器是会提示错误的( error CS0663: 'foo' cannot define overloaded methods that differ only on ref and out)。其原因是,C#编译器,对ref和out生成的IL代码,是相同的;而在CLR层面,是没有ref和out的区别的。C#中,ref和out的区别,主要是,谁负责初始化这个参数使之能用——ref形式是函数外初始化,而out是函数内初始化。

你可能感兴趣的:(C# - 函数参数的传递)