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

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

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

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

Почему это?

Ответ 1

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

Если вы хотите использовать передачу по ссылке, вы должны использовать out или ref независимо от того, является ли тип параметра типом значения или ссылочным типом. В этом случае, по сути, сама переменная передается по ссылке, поэтому параметр использует то же место хранения, что и аргумент, - и вызывающий видит изменения в самом параметре.

Так:

public void Foo(Image image)
{
    // This change won't be seen by the caller: it changing the value
    // of the parameter.
    image = Image.FromStream(...);
}

public void Foo(ref Image image)
{
    // This change *will* be seen by the caller: it 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 changing the data
    // within the object that the parameter value refers to.
    image.RotateFlip(...);
}

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

Ответ 2

Еще один пример кода, чтобы продемонстрировать это:

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

И вывод:

TestPlain: 0

TestRef: 5

TestObjPlain: тест

TestObjRef: TestObjRef

Ответ 3

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

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

Ответ 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
}

Ответ 6

In Pass By Reference Вы добавляете только "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);  
        }
    }
}

Ответ 7

Принятый ответ звучит немного неправильно и запутанно. Что такое "копия справки"?

Как имеет смысл следующее утверждение:

"Однако изменение значения параметра для ссылки на другой объект не будет отображаться, когда вы используете значение pass by value, которое является значением по умолчанию для всех типов." Передача по значению не является значением по умолчанию для всех типов.

Его пример в его ссылке пытается установить экземпляр объекта в null. Объект не был успешно установлен в null из-за автоматической сборки мусора. Его нельзя удалить таким образом.

Вот статья Microsoft, сравнивающая Java и С#.

От https://msdn.microsoft.com/en-us/library/ms836794.aspx

"Все объекты являются ссылками

Типы ссылок очень похожи на указатели на С++, особенно при установке идентификатора для некоторого экземпляра нового класса. Но при доступе к свойствам или методам этого ссылочного типа используйте ".". оператора, который похож на доступ к экземплярам данных на С++, которые создаются в стеке. Все экземпляры классов создаются в куче с помощью нового оператора, но удаление не допускается, так как оба языка используют свои собственные схемы сбора мусора, которые обсуждаются ниже. "