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

问题描述 投票:0回答:9

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

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

但是,我注意到当我传递一个

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

这是为什么?

c# parameter-passing pass-by-reference pass-by-value
9个回答
689
投票

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

如果要使用引用传递,则必须使用

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

所以:

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(...);
}

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


114
投票

添加了很多好的答案。我仍然想做出贡献,也许它会稍微澄清一些。

当您将实例作为参数传递给方法时,它会传递实例的

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


25
投票

还有一个代码示例来展示这一点:

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


19
投票

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


9
投票

当您将

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
}

4
投票

如何将对象传递给方法?

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

ref

以下链接可以给您更好的想法。

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


3
投票
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";
  }
}

-2
投票

在按引用传递中,您只需在函数参数中添加“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);  
        }
    }
}

-3
投票

在最新版本的 C#(撰写本文时为 C# 9)中,对象默认通过

ref
传递。因此,对调用函数中的对象所做的任何更改都将保留在被调用函数中的对象中。

© www.soinside.com 2019 - 2024. All rights reserved.