按引用传递引用 vs 按值传递引用 - C#

3

您好,

我了解按值传递和按引用传递之间的区别。但是,将引用(例如数组)通过引用传递和按值传递数组是我无法理解的问题。您如何通过引用传递引用?

     int[] myArray = {1,2,3};
     PassByVal(myArray);
     PassByRef(ref myArray);

     PassByVal(int[] array)
     {    array = new int[] {7,8,9};   // will not work }

     PassByRef(ref int[] array)
     {    array = new int[] {10,11,12}; }  // will work

你在什么情境下听到这个的?有没有与ref by ref相关的例子? - BeemerGuy
Interlocked.Exchange 是一个很好的例子。它需要引用要更改的对象变量。http://msdn.microsoft.com/en-us/library/bb337971.aspx - CodesInChaos
如果您的问题已得到解答,请将其中一个答案标记为答案。 - Shiv Kumar
4个回答

10
如果您通过引用传递引用,则可以将传递的变量指向一个新对象。如果您按值传递引用,仍然可以更改对象的状态,但无法使变量指向不同的对象。
示例:
void RefByRef(ref object x)
{
  x=new object(2);
}

void RefByValue(object x)
{
 x=new object(2);//Only changes a local variable and gets discarded once the function exits
}

void Test()
{
  object x1=1;
  object x1a=x1;
  RefByRef(ref x1);
  //x1 is now a boxed 2
  //x1a is still a boxed 1


  object x2=1;
  RefByValue(x2);
  //x2 is still a boxed 1
}

2
我建议你查看这个链接。它非常有用,包含关于C#中的参数传递的简单示例。
引用参数不会传递函数成员调用中使用的变量的值 - 它们使用变量本身。 与在函数成员声明中创建变量的新存储位置不同,使用相同的存储位置,因此函数成员中的变量值和引用参数的值始终相同。引用参数需要ref修饰符作为声明和调用的一部分 - 这意味着当你通过引用传递某些内容时,它总是很清楚。让我们看一下我们之前的示例,只需将参数更改为引用参数:
void Foo (ref StringBuilder x) {
    x = null;
}

...

StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (ref y);
Console.WriteLine (y==null); // will write TRUE

在你的例子中
int[] myArray = {1,2,3};
PassByVal(myArray);
PassByRef(ref myArray);

PassByVal(int[] array){
    // the function copy the value of the pointer in a new location of memory
    // the "copied" pointer still points to the array 123    

    // now you are modifying the object pointed by THE COPY of the pointer
    // the original pointer still points to array 123
    // the copy of the pointer will point to array 456
    array = new int[] {7,8,9}; 

} // will not work

PassByRef(ref int[] array){
   // here you are passing the pointer without creating a copy of it in a 
   // new location of memory

   // we have not a original pointer and a "copyed" pointer
   // we have only the original pointer and now whe point it to array 10,11,12
   array = new int[] {10,11,12}; 
}  // will work

谢谢,这是一个非常有用的链接。我猜书籍并没有详细解释引用是如何工作的,它们只是提到对象通过引用传递... - user402524
我写了一个新的响应代码。请看“在你的例子中”。 - Marco Staffoli

2
为了回答您的问题,让我们先来看一下ValueTypes。ValueType保存值。也就是说,它不会指向另一个保存值的内存位置,而是它的内存位置就是该值。
因此,
int i = 10;
int j = i;
这里发生的是将i的值的副本赋给j。它们都有相同的值,但它们在内存中是不同的位置。换句话说,每次将valuetype分配给另一个valuetype时,都会进行复制。
与之相反的是ReferenceTypes。
object o = 10;
object p = o;
因为o是ReferenceType,所以o指向一个保存值为10的内存位置(实际上它是装箱的,但我会保持简单)。在下一行中,p现在指向相同的内存位置。换句话说,引用类型有两个要素: 1.地址指针 2.实际“物体”所在的内存位置(该地址指向)。
如果您已经理解到这里,那么我们可以继续探讨按值传递和按引用传递。
在C#中,参数是按值传递的。因此,如果您将valueType传递给期望valuetype参数的方法,则:
int i = 10;
SomeMethod(i);
Console.WriteLine(i);

static void SomeMethod(int value)
{
  value = 20;
}

当调用SomeMethod方法时,i的值的副本被发送到该方法。如果该方法操作参数,则不会影响原始变量i。因此,在控制台窗口中看到的是10;

与引用类型相比,这种情况是不同的;

  class Program
  {
    static void Main(string[] args)
    {
      Customer c = new Customer() { Name = "Mike" };
      SomeMethod(c);
      Console.WriteLine(c.Name);
    }

    static void SomeMethod(Customer customer)
    {
      customer.Name = "John";
    }
  }

  class Customer
  {
    public string Name { get; set; }
  }

由于c是引用类型,而C#按值传递参数。因此传递的是引用值的“值”的副本。也就是说,传递了c指向的地址的值。在方法中,由于地址相同(虽然是副本,但指向同一内存位置),方法能够操作对象的状态。因此,在控制台窗口中看到的是“John”,而不是“Mike”。
但是,如果该方法尝试将另一个实例分配给参数(在这种情况下称为“customer”),那么情况就会改变。
  class Program
  {
    static void Main(string[] args)
    {
      Customer c = new Customer() { Name = "Mike" };
      SomeMethod(c);
      Console.WriteLine(c.Name);
    }

    static void SomeMethod(Customer customer)
    {
      customer = new Customer();
      customer.Name = "John";
    }
  }

  class Customer
  {
    public string Name { get; set; }
  }

请注意,在该方法中,我们创建了一个名为Customer的新实例,并将其分配给参数customer,然后将此新实例的名称设置为“John”。在控制台窗口中,我们将看到“Mike”,而不是john。
这是因为在将变量(c)传递给方法之前,复制了原始变量的副本。现在在方法中,我们有另一个地址,然后操作该新地址,因此原始实例保持不变。明白了吗?
好的,如果这有意义。那么,如果我们确实希望SomeMethod能够执行我们尝试执行的操作呢?那么,参数不能按值传递,而必须按引用传递。这意味着变量c和两个部分(它所指向的地址的值和地址本身)被传递。因此,现在您正在按引用传递引用类型。
  class Program
  {
    static void Main(string[] args)
    {
      Customer c = new Customer() { Name = "Mike" };
      SomeMethod(ref c);
      Console.WriteLine(c.Name);
    }

    static void SomeMethod(ref Customer customer)
    {
      customer = new Customer();
      customer.Name = "John";
    }
  }

  class Customer
  {
    public string Name { get; set; }
  }

0
这可能看起来有点混淆,但实际上并不那么棘手。当你将引用类型的实例赋给一个变量时,你可以说这个变量的“值”将是一个对对象的引用,而非对象本身。当你将该变量按值传递到另一个方法时,你正在传递引用的副本。被调用的方法将会“看到”与调用代码相同的实例。如果你改为通过引用传递变量,则调用方法将会看到与调用代码相同的引用副本。
这两种行为之间的差异在于,当你通过引用传递变量时,被调用的方法可能会将另一个引用赋给该变量(让它引用同一类型的另一个实例),而调用代码将看到这种更改。除非你进行这样的分配,否则没有必要使用 ref

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接