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

本文关键字:值传 对象 引用 | 更新日期: 2023-09-27 18:19:21

在c#中,我一直认为非基本变量是按引用传递的,基本值是按值传递的。

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

然而,我注意到,当我通过一个System.Drawing.Image对象,这似乎不是情况?如果我将system。drawing。image对象传递给另一个方法,并将图像加载到该对象上,然后让该方法离开作用域并返回调用方法,图像没有加载到原始对象上?

为什么会这样?

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

对象根本不传递。默认情况下,计算实参,并按值传递它的,作为您正在调用的方法的形参的初始值。现在重要的一点是,该值是引用类型的引用——一种获取对象(或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(...);
}

我有一篇文章详细介绍了这一点。基本上,"通过引用传递"并不是你想的那样。

已经添加了许多好的答案。我还是想贡献一下,也许会更清楚一点。

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

再来一个代码示例:

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

我想这样做会更清楚。我建议下载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";
}

应该输出

WontUpdate

名:Egli,姓:Becerra

UpdateImplicitly

名:Favio,姓:Becerra

UpdateExplicitly

名:Favio,姓:Becerra

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

所以如果在那个方法中你要加载一个新图像你会使用new/copy reference来加载。

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
}

如何将对象传递给方法?

你是否在对象的方法中做了new操作?如果是这样,你必须在方法中使用ref

下面的链接给你更好的想法。

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

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

在Pass By Reference中,您只在函数参数中添加"ref"和1更重要的是,你应该声明函数"静态",因为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# 9中,对象默认由ref传递。因此,对调用函数中的对象所做的任何更改都将保留在被调用函数中的对象中。