在C#中按引用或值传递对象


233

在C#中,我一直认为非原始变量是通过引用传递的,原始值是通过值传递的。

因此,当将任何非原始对象传递给方法时,对该方法中的对象执行的任何操作都会影响要传递的对象。(C#101的东西)

但是,我注意到当我传递System.Drawing.Image对象时,似乎不是这样吗?如果我将system.drawing.image对象传递给另一个方法,然后将图像加载到该对象上,然后让该方法超出范围并返回到调用方法,则该图像未加载到原始对象上吗?

为什么是这样?


20
默认情况下,所有变量在C#中均按值传递。对于引用类型,您要传递引用的值
安德鲁·巴伯

Answers:


502

根本不传递对象。默认情况下,对参数进行求值,并按将其作为您所调用方法的参数的初始值传递。现在重要的一点是,该值是对引用类型的引用-一种访问对象(或为null)的方法。对该对象的更改将在呼叫者处可见。但是,当您使用按值传递时,更改参数的值以引用其他对象将可见,这是所有类型的默认值。

如果要使用按引用传递,则无论参数类型是值类型还是引用类型,都必须使用outref。在这种情况下,变量本身实际上是通过引用传递的,因此参数使用与参数相同的存储位置-并且调用者可以看到对参数本身的更改。

所以:

public void Foo(Image image)
{
    // This change won't be seen by the caller: it's changing the value
    // of the parameter.
    image = Image.FromStream(...);
}

public void Foo(ref Image image)
{
    // This change *will* be seen by the caller: it's changing the value
    // of the parameter, but we're using pass by reference
    image = Image.FromStream(...);
}

public void Foo(Image image)
{
    // This change *will* be seen by the caller: it's changing the data
    // within the object that the parameter value refers to.
    image.RotateFlip(...);
}

我有一篇文章对此进行了更详细的介绍。基本上,“通过引用”并不意味着您认为它意味着什么。


2
你的权利,我没看到!我加载image = Image.FromFile(..),那是替换变量image而不更改对象!:) 当然。
迈克尔,2012年

1
@Adeem:不完全-没有“参数对象”,存在参数值所引用的对象。我认为您有正确的主意,但术语很重要:)
Jon Skeet 2015年

2
如果我们从c#中删除关键字refout从c#中删除,可以肯定地说c#传递参数的方式与java相同,即始终按值传递。与Java有什么区别。
宽带

1
@broadband:是的,默认的传递模式是按值。尽管C#当然具有指针和自定义值类型,但这使得它们都比Java中复杂。
乔恩·斯基特

3
@Vippy:不,一点也不。这是参考文献的副本。我建议您阅读链接的文章。
乔恩·斯基特

18

另一个代码示例展示了这一点:

void Main()
{


    int k = 0;
    TestPlain(k);
    Console.WriteLine("TestPlain:" + k);

    TestRef(ref k);
    Console.WriteLine("TestRef:" + k);

    string t = "test";

    TestObjPlain(t);
    Console.WriteLine("TestObjPlain:" +t);

    TestObjRef(ref t);
    Console.WriteLine("TestObjRef:" + t);
}

public static void TestPlain(int i)
{
    i = 5;
}

public static void TestRef(ref int i)
{
    i = 5;
}

public static void TestObjPlain(string s)
{
    s = "TestObjPlain";
}

public static void TestObjRef(ref string s)
{
    s = "TestObjRef";
}

并输出:

测试平原:0

测试编号:5

TestObjPlain:测试

TestObjRef:TestObjRef


2
因此,如果我们想查看调用方函数中的更改,基本上仍然需要通过引用类型作为引用。
坚不可摧的

1
字符串是不可变的引用类型。不可变意味着,它在创建后就无法更改。对字符串的每次更改都会创建一个新字符串。这就是为什么需要将字符串作为“ ref”传递来更改调用方法的原因。其他对象(例如员工)也可以不带“ ref”来传递,以将更改返回给调用方法。
喜马拉雅山

1
@vmg,根据HimalayaGarg,这不是一个很好的例子。您需要包括另一个不变的引用类型示例。
Daniel

11

许多好的答案已被添加。我仍然想贡献一点,也许会有所澄清。

当您将实例作为参数传递给方法时,它将传递copy实例的。现在,如果传递的实例是value type(驻留在中stack),则传递该值的副本,因此,如果您修改它,它将不会反映在调用者中。如果实例是引用类型,则将引用的副本(再次位于中stack)传递给对象。因此,您获得了对同一对象的两个引用。它们都可以修改对象。但是,如果在方法主体中实例化了新对象,则引用的副本将不再引用原始对象,而将引用刚创建的新对象。因此,您最终将拥有2个引用和2个对象。


这应该是选择的答案!
JAN

我完全同意!:)
JOSEFtw

8

我想当您这样做时,它会更加清晰。我建议下载LinqPad来测试类似的事情。

void Main()
{
    var Person = new Person(){FirstName = "Egli", LastName = "Becerra"};

    //Will update egli
    WontUpdate(Person);
    Console.WriteLine("WontUpdate");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateImplicitly(Person);
    Console.WriteLine("UpdateImplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateExplicitly(ref Person);
    Console.WriteLine("UpdateExplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");
}

//Class to test
public class Person{
    public string FirstName {get; set;}
    public string LastName {get; set;}

    public string printName(){
        return $"First name: {FirstName} Last name:{LastName}";
    }
}

public static void WontUpdate(Person p)
{
    //New instance does jack...
    var newP = new Person(){FirstName = p.FirstName, LastName = p.LastName};
    newP.FirstName = "Favio";
    newP.LastName = "Becerra";
}

public static void UpdateImplicitly(Person p)
{
    //Passing by reference implicitly
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

public static void UpdateExplicitly(ref Person p)
{
    //Again passing by reference explicitly (reduntant)
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

那应该输出

更新

名:Egli,姓:Becerra

隐式更新

名:Favio,姓:Becerra

明确更新

名:Favio,姓:Becerra


以及公共静态无效WhatWhat(Person p){p = new Person(){FirstName =“ First”,LastName =“ Last”}};以及 }。:)
马林·波波夫

4

当您将System.Drawing.Image类型对象传递给方法时,实际上是在传递对该对象的引用副本。

因此,如果在该方法中要加载新图像,则要使用新的/已复制的引用来加载。您没有更改原件。

YourMethod(System.Drawing.Image image)
{
    //now this image is a new reference
    //if you load a new image 
    image = new Image()..
    //you are not changing the original reference you are just changing the copy of original reference
}


-1

在“按引用传递”中,您只需在函数参数中添加“ ref”,由于main是static(#public void main(String[] args)),您应该声明函数“ static” !

namespace preparation
{
  public  class Program
    {
      public static void swap(ref int lhs,ref int rhs)
      {
          int temp = lhs;
          lhs = rhs;
          rhs = temp;
      }
          static void Main(string[] args)
        {
            int a = 10;
            int b = 80;

  Console.WriteLine("a is before sort " + a);
            Console.WriteLine("b is before sort " + b);
            swap(ref a, ref b);
            Console.WriteLine("");
            Console.WriteLine("a is after sort " + a);
            Console.WriteLine("b is after sort " + b);  
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.