Передача об'єктів за посиланням або значенням у C #


233

У C # я завжди думав, що непримітивні змінні передаються за посиланням, а примітивні значення передаються за значенням.

Отже, переходячи до методу будь-який непомітний об'єкт, все, що робиться об'єкту в методі, вплине на переданий об'єкт. (C # 101 речі)

Однак я помітив, що коли я передаю об'єкт System.Drawing.Image, це, здається, не так? Якщо я передаю об'єкт system.drawing.image іншому методу і завантажую зображення на цей об’єкт, то відпустіть цей метод поза сферою дії та повернуться до викликового методу, це зображення не завантажується на вихідний об'єкт?

Чому це?


20
Усі змінні передаються за значенням за замовчуванням у C #. Ви передаєте значення посилання у випадку типів посилань.
Ендрю Барбер

Відповіді:


502

Об'єкти взагалі не передаються. За замовчуванням аргумент оцінюється і його значення передається за значенням як початкове значення параметра методу, який ви викликаєте. Тепер важливим моментом є те, що значення є посиланням на еталонні типи - спосіб потрапляння на об’єкт (або нуль). Зміни в цьому об’єкті будуть видні у абонента. Однак зміна значення параметра для посилання на інший об'єкт не буде видно, коли ви використовуєте значення pass by, що є типовим для всіх типів.

Якщо ви хочете використовувати пропускний посилання, ви повинні використовувати 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


2
Таким чином, в основному тип посилання все ще НЕОБХІДНО проходити як опорний, якщо ми хочемо побачити зміни функції Caller.
Незламний

1
Рядки є незмінними еталонними типами. Незмінний засіб, його неможливо змінити після його створення. Кожна зміна рядка створить новий рядок. Ось чому рядки потрібно передавати як "ref", щоб змінити метод виклику. Інші об'єкти (наприклад, працівник) можуть передаватися без 'ref', щоб повернути зміни в методі виклику.
Гімалаї Гарг

1
@vmg, як свідчить HimalayaGarg, це не дуже вдалий приклад. Потрібно включити ще один приклад типового типу, який не змінюється.
Даніель

11

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

Коли ви передаєте екземпляр як аргумент методу, він передає copyекземпляр. Тепер, якщо екземпляр, який ви передаєте, - це value type(знаходиться в stack), ви передаєте копію цього значення, тож якщо ви модифікуєте його, воно не відображатиметься на абоненті. Якщо екземпляр є посилальним типом, ви передаєте копію посилання (знову знаходиться в точці stack) об'єкту. Отже, ви отримали дві посилання на один і той же об’єкт. Обидва вони можуть змінювати об’єкт. Але якщо в тілі методу ви інстанціюєте новий об'єкт, ваша копія посилання більше не буде посилатися на оригінальний об'єкт, він буде посилатися на новий створений вами об’єкт. Таким чином, у вас з’явиться 2 посилання та 2 об’єкти.


Це має бути обрана відповідь!
JAN

Я повністю згоден! :)
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

Ім'я: Фавіо, Прізвище: Бесерра

Оновити явно

Ім'я: Фавіо, Прізвище: Бесерра


а як щодо публічної статичної пустоти What About (Person p) {p = new Person () {FirstName = "Перший", LastName = "Останній"}; }. :)
Марин Попов

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

У програмі 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);  
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.