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

Posted

技术标签:

【中文标题】在 C# 中通过引用或值传递对象【英文标题】:Passing Objects By Reference or Value in C# 【发布时间】:2018-04-03 11:52:45 【问题描述】:

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

因此,当将任何非原始对象传递给方法时,对方法中的对象所做的任何事情都会影响正在传递的对象。 (C# 101 的东西)

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

这是为什么?

【问题讨论】:

所有变量在C#中默认都是传值的。在引用类型的情况下,您正在传递 引用的值 同样的事情:why-is-list-when-passed-without-ref-to-a-function-acting-like-passed-with-ref 由于没有给出代码,所以不清楚要问什么。也许 OP 的意思是 image.Load(filename) 或者他们的意思是 image = Image.Load(filename) 其中 image 是函数参数。 【参考方案1】:

对象 根本不通过。默认情况下,对参数进行求值,并将其 value 作为您正在调用的方法的参数的初始值按值传递。现在重要的一点是,该值是对引用类型的引用——一种获取对象(或 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(...);

我有一个article which goes into a lot more detail in this。基本上,“通过引用传递”并不意味着您认为它意味着什么。

【讨论】:

你说得对,我没看到!我加载 image = Image.FromFile(..) 并且替换了变量 image 而不是更改对象! :) 当然。 @Adeem:不完全是——没有“参数对象”,有参数值所指的对象。我认为您的想法是正确的,但术语很重要:) 如果我们从 c# 中删除关键字 refout,是否可以说 c# 以与 java 相同的方式传递参数,即始终按值传递。和java有什么区别。 @broadband:是的,默认的传递方式是按值传递。当然,C# 有指针和自定义值类型,这使得它比 Java 复杂一些。 @Vippy:不,一点也不。它是 reference 的副本。我建议你阅读链接的文章。【参考方案2】:

添加了很多好的答案。我仍然想贡献,可能会稍微澄清一点。

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

【讨论】:

这应该是选择的答案! 我完全同意! :)【参考方案3】:

另外一个代码示例来展示这一点:

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";

还有输出:

TestPlain:0

TestRef:5

TestObjPlain:测试

TestObjRef:TestObjRef

【讨论】:

所以如果我们想看到调用函数的变化,基本上引用类型仍然需要作为引用传递。 字符串是不可变的引用类型。不可变的意思是,创建后不能更改。对字符串的每次更改都会创建一个新字符串。这就是为什么需要将字符串作为 'ref' 传递以更改调用方法的原因。其他对象(例如员工)可以在没有 'ref' 的情况下传递,以在调用方法中取回更改。 @vmg,根据 HimalayaGarg,这不是一个很好的例子。您需要包含另一个不可变的引用类型示例。【参考方案4】:

我想当你这样做时它会更清楚。我建议下载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

隐式更新

名字:法维奥,姓氏:贝塞拉

显式更新

名字:法维奥,姓氏:贝塞拉

【讨论】:

那么 public static void WhatAbout(Person p) p = new Person()FirstName = "First", LastName = "Last"; 。 :) 感谢 lingpad 的东西 Linux4Life531 试试这个而不是 linqpad 它也是免费的...dotnetfiddle.net 同样的东西,你不需要下载【参考方案5】:

当您将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

【讨论】:

【参考方案6】:

你是如何将对象传递给方法的?

您是否在该方法中为对象执行新操作?如果是这样,您必须在方法中使用ref

下面的链接给你更好的主意。

http://dotnetstep.blogspot.com/2008/09/passing-reference-type-byval-or-byref.html

【讨论】:

【参考方案7】:

在最新版本的 C# 中,在撰写本文时是 C# 9,对象默认由 ref 传递。因此,对调用函数中的对象所做的任何更改都将保留在被调用函数中的对象中。

【讨论】:

这对我来说似乎不是这样...... 你的来源是什么?本月发布的Documentation 没有提到这一点。 documentation 也不用于传递引用类型。【参考方案8】:
Employee e = new Employee();
e.Name = "Mayur";

//Passes the reference as value. Parameters passed by value(default).
e.ReferenceParameter(e);

Console.WriteLine(e.Name); // It will print "Shiv"

 class Employee 

   public string Name  get; set; 

   public void ReferenceParameter(Employee emp) 

     //Original reference value updated.
    emp.Name = "Shiv";

    // New reference created so emp object at calling method will not be updated for below changes.
    emp = new Employee();
    emp.Name = "Max";
  

【讨论】:

【参考方案9】:

在传递引​​用中你只在函数参数中添加“ref”和一个 由于 main 是静态的(#public void main(String[] args)),您应该声明函数“静态”!

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);  
        
    

【讨论】:

以上是关于在 C# 中通过引用或值传递对象的主要内容,如果未能解决你的问题,请参考以下文章

通过引用或值传递的数组[重复]

在 C++ 中通过引用/值传递

通过引用或值传递对象

在 C++ 中通过引用传递对象

C# vs Python 参数传递

有没有办法在函数调用中通过引用传递和通过值显式传递?