Передача объектов по ссылке или значению в C #

234

В C # я всегда думал, что непримитивные переменные передаются по ссылке, а примитивные значения передаются по значению.

Таким образом, при передаче методу любого не примитивного объекта, все, что делается с объектом в методе, будет влиять на передаваемый объект. (C # 101 материал)

Тем не менее, я заметил, что когда я передаю объект System.Drawing.Image, что это не так? Если я передам объект system.drawing.image другому методу и загрузлю изображение в этот объект, то позволю этому методу выйти из области видимости и вернуться к вызывающему методу, это изображение не загружено в исходный объект?

Почему это?

Майкл
источник
20
Все переменные передаются по значению по умолчанию в C #. Вы передаете значение ссылки в случае ссылочных типов.
Эндрю Барбер

Ответы:

503

Объекты не передаются вообще. По умолчанию аргумент оценивается, и его значение передается по значению как начальное значение параметра вызываемого вами метода. Теперь важным моментом является то, что значение является ссылкой для ссылочных типов - способ добраться до объекта (или ноль). Изменения в этом объекте будут видны из звонящего. Однако изменение значения параметра для ссылки на другой объект не будет отображаться при использовании передачи по значению, которая используется по умолчанию для всех типов.

Если вы хотите использовать передачу по ссылке, вы должны использовать 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(...);
}

У меня есть статья, которая более подробно рассказывает об этом . По сути, «передача по ссылке» не означает, что вы думаете, что это значит.

Джон Скит
источник
2
Ваше право, я этого не видел! Я загружаю image = Image.FromFile (..), и это заменяет переменное изображение, а не изменяет объект! :) конечно.
Майкл
1
@Adeem: Не совсем - нет «объекта параметра», есть объект, к которому относится значение параметра. Я думаю, у вас есть правильная идея, но терминология имеет значение :)
Джон Скит
2
Если мы отбрасываем ключевые слова refи outиз c #, можно ли сказать, что c # передает параметры так же, как это делает java, то есть всегда по значению. Есть ли разница с Java.
широкополосный
1
@broadband: Да, режим передачи по умолчанию - по значению. Хотя, конечно, в C # есть указатели и пользовательские типы значений, что делает все это немного сложнее, чем в Java.
Джон Скит
3
@Vippy: Нет, совсем нет. Это копия ссылки . Я предлагаю вам прочитать связанную статью.
Джон Скит
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 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

VMG
источник
2
Таким образом, ссылочный тип по-прежнему ДОЛЖЕН БЫТЬ ПРОЙДЕН в качестве ссылки, если мы хотим увидеть изменения в функции Caller.
Unbreakable
1
Строки являются неизменяемыми ссылочными типами. Неизменный означает, что он не может быть изменен после того, как он был создан. Каждое изменение строки создаст новую строку. Вот почему строки должны быть переданы как 'ref', чтобы получить изменение в вызывающем методе. Другие объекты (например, сотрудник) могут быть переданы без 'ref', чтобы вернуть изменения в вызывающий метод.
Гималаи Гарг
1
@vmg, согласно HimalayaGarg, это не очень хороший пример. Вы должны включить другой пример ссылочного типа, который не является неизменным.
Даниил
11

Много хороших ответов было добавлено. Я все еще хочу внести свой вклад, возможно, это прояснит немного больше.

Когда вы передаете экземпляр в качестве аргумента методу, он передает copyэкземпляр. Теперь, если передаваемый вами экземпляр является value type(находится в stack), вы передаете копию этого значения, поэтому, если вы измените его, оно не будет отражено в вызывающей программе. Если экземпляр является ссылочным типом, вы передаете копию ссылки (снова находится вstack ) объекту. Итак, вы получили две ссылки на один и тот же объект. Оба они могут изменить объект. Но если в теле метода вы создадите новый объект, ваша копия ссылки больше не будет ссылаться на исходный объект, она будет ссылаться на новый объект, который вы только что создали. Таким образом, вы получите 2 ссылки и 2 объекта.

OlegI
источник
Это должен быть выбранный ответ!
ЯНВ
Я полностью согласен! :)
JOSEFtw
8

Я думаю, это понятнее, когда вы делаете это так. Я рекомендую скачать 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

Имя: Эгли, Фамилия: Бесерра

UpdateImplicitly

Имя: Фавио, Фамилия: Бесерра

UpdateExplicitly

Имя: Фавио, Фамилия: Бесерра

Эгли Бесерра
источник
а как насчет публичной статической пустоты WhatAbout (Person p) {p = new Person () {FirstName = "First", LastName = "Last"}; } :)
Марин Попов
4

Когда вы передаете 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
}
Харис Хасан
источник
-1

При передаче по ссылке вы только добавляете «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);  
        }
    }
}
user5593590
источник