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

mic*_*ael 217 c# parameter-passing pass-by-reference pass-by-value

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

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

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

为什么是这样?

Jon*_*eet 459

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

如果要使用pass-by-reference,则必须使用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(...);
}
Run Code Online (Sandbox Code Playgroud)

我有一篇文章详细介绍了这一点.基本上,"通过引用传递"并不意味着您认为它意味着什么.

  • @Vippy:不,一点也不.它是*参考*的副本.我建议你阅读链接的文章. (3认同)
  • 啊啊,示例 1 和 3 将指针(地址)的副本传递给源引用类型,因此最初源和本地事物都在同一对象的同一位置查找,但示例 1 更改了本地副本内部的指针方法,所以现在正在查看其他内容,原始指针未被触及,因为它无法被触及。示例 2(参考)对两者使用相同的指针,因此如果您弄乱它,它就会改变两者。 (3认同)
  • 你的权利,我没看到!我加载image = Image.FromFile(..),那是替换变量image而不更改对象!:) 当然。 (2认同)
  • 如果我们从c#中删除关键字`ref`和`out`,可以说c#以与java相同的方式传递参数,即总是按值传递.与java有什么区别. (2认同)
  • @DJMacetMan:是的,完全正确。这不会改变参数(假设 `myobj` 是某种引用类型......) (2认同)

Ole*_*egI 51

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

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

  • 这应该是选择的答案! (3认同)

vmg*_*vmg 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 TestRef(ref int i)
{
    i = 5;
}

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

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

public static void TestObjPlain(string s)
{
    s = "TestObjPlain";
}
Run Code Online (Sandbox Code Playgroud)

并输出:

TestPlain:0

TestRef:5

TestObjPlain:测试

TestObjRef:TestObjRef

  • 字符串是不可变的引用类型。不可变意味着它创建后就不能更改。对字符串的每次更改都会创建一个新字符串。这就是为什么需要将字符串作为“ref”传递以获取调用方法中的更改的原因。其他对象(例如员工)可以在没有“ref”的情况下传递,以在调用方法中获取更改。 (5认同)
  • 因此,如果我们想查看调用方函数中的更改,基本上仍然需要通过引用类型作为引用。 (2认同)
  • @vmg,根据 HimalayaGarg,这不是一个很好的例子。您需要包含另一个不是不可变的引用类型示例。 (2认同)

Egl*_*rra 6

当你这样做的时候我觉得它更清楚了.我建议下载LinkPad来测试这样的东西.

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";
}
Run Code Online (Sandbox Code Playgroud)

这应该输出

WontUpdate

名:Egli,姓:Becerra

UpdateImplicitly

名:Favio,姓:Becerra

UpdateExplicitly

名:Favio,姓:Becerra


Har*_*san 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
}
Run Code Online (Sandbox Code Playgroud)