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

Passing Objects By Reference or Value in C#

提问人:Michael 提问时间:1/3/2012 最后编辑:nawfalMichael 更新时间:9/29/2021 访问量:342423

问:

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

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

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

为什么会这样?

C# 参数传递 引用

评论

28赞 Andrew Barber 1/3/2012
默认情况下,所有变量在 C# 中都按值传递。在引用类型的情况下,您传递的是引用的值
0赞 nawfal 10/13/2016
同样的事情:why-is-list-when-passed-without-ref-to-a-function-acting-like-passed-with-ref
0赞 StayOnTarget 4/13/2021
由于没有给出代码,因此不清楚要问什么。也许 OP 的意思,或者他们的意思是函数参数在哪里。image.Load(filename)image = Image.Load(filename)image

答:

3赞 dotnetstep 1/3/2012 #1

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

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

以下链接为您提供更好的主意。

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

670赞 Jon Skeet 1/3/2012 #2

根本不传递对象。默认情况下,将计算参数,并按 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(...);
}

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

评论

2赞 Michael 1/3/2012
你说得对,我没看到!我加载图像 = Image.FromFile(..) 并且替换了变量 image 而不是更改对象!:)答案是肯定的。
2赞 broadband 6/3/2015
如果我们从 c# 中删除关键字和,是否可以说 c# 以与 java 相同的方式传递参数,即始终按值传递。和 java 有什么区别吗?refout
2赞 Jon Skeet 8/17/2016
@DJMethaneMan:是的,没错。这不会改变参数(假设是某种参考类型......myobj
5赞 Jon Skeet 8/20/2016
@Vippy:不,一点也不。它是参考资料的副本。我建议你阅读链接的文章。
3赞 CAD bloke 5/7/2020
啊,示例 1 和 3 将指针(地址)的副本传递给源引用类型,因此最初源和本地事物都在同一个对象的同一位置查找,但示例 1 更改了方法内本地副本的指针,所以现在它正在查看其他内容, 原始指针未被触及,因为它无法被触及。示例 2 (ref) 对两者都使用相同的指针,因此如果您弄乱了它,它会同时更改两者。
7赞 Haris Hasan 1/3/2012 #3

当您将类型对象传递给某个方法时,您实际上是在向该对象传递引用的副本。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
}

评论

0赞 Mohamed Fathallah 4/13/2023
这是正确的解释方式。用于二叉树和 C 语言中的许多非线性数据结构#
23赞 vmg 2/7/2014 #4

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

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

评论

4赞 Unbreakable 6/3/2018
因此,如果我们想查看 Caller 函数中的更改,基本上引用类型仍然需要作为引用传递。
7赞 Himalaya Garg 2/8/2020
字符串是不可变的引用类型。不可变意味着,它在创建后无法更改。对字符串的每次更改都会创建一个新字符串。这就是为什么字符串需要作为“ref”传递才能更改调用方法的原因。其他对象(例如 employee)可以在不带“ref”的情况下传递,以在调用方法中获取更改。
4赞 Daniel 3/4/2020
@vmg,根据 HimalayaGarg 的说法,这不是一个很好的例子。您需要包含另一个不是不可变的引用类型示例。
-1赞 user5593590 11/23/2015 #5

在传递引用中,您只在函数参数中添加“ref”和一个 您应该声明函数“static”的更多内容,因为 main 是 static(#)!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);  
        }
    }
}
17赞 Egli Becerra 9/16/2016 #6

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

这应该输出

不会更新

名字:埃格利,姓氏:贝塞拉

UpdateImplicitly

名字:Favio,姓氏:Becerra

UpdateExplicitly

名字:Favio,姓氏:Becerra

评论

0赞 Marin Popov 7/4/2019
公共静态 void WhatAbout(Person p) { p = new Person(){FirstName = “First”, LastName = “Last”}; } .:)
1赞 James Ashwood 10/20/2020
谢谢你的lingpad的东西
0赞 Egli Becerra 10/21/2020
Linux4Life531 试试这个,而不是 linqpad,它也是免费的......dotnetfiddle.net 同样的事情,你不需要下载
107赞 OlegI 11/29/2019 #7

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

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

评论

6赞 JAN 1/22/2020
这应该是选择的答案!
1赞 JOSEFtw 1/22/2020
我完全同意!:)
0赞 Vivek Kaushik 10/14/2022
很有道理。非常感谢你,先生。
0赞 bp4D 4/9/2023
希望对来自 Java 世界的人有所帮助:在这方面,c# 本质上与 Java 相同。但是,不同的是通过 ref 关键字传递引用。看看这个 - learn.microsoft.com/en-us/dotnet/csharp/language-reference/...
-1赞 Sujoy 9/3/2021 #8

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

评论

1赞 XRaycat 10/16/2021
对我来说似乎不是这样......
1赞 ProgrammingLlama 1/17/2022
你的消息来源是什么?本月发布的文档没有提到这一点。传递引用类型的文档也没有。
1赞 Michael 3/16/2023
@ProgrammingLlama 确实如此,如果在之前的 8 个版本之后就这样更改,这似乎会破坏很多现有代码。
2赞 Mayur Pawar 9/29/2021 #9
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";
  }
}