Навіщо використовувати ключове слово 'ref' при передачі об'єкта?


290

Якщо я передаю об'єкт методу, чому я повинен використовувати ключове слово ref? Чи не так це поведінка за замовчуванням?

Наприклад:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

Вихід "Bar", що означає, що об'єкт був переданий як еталон.

Відповіді:


299

Пройдіть, refякщо ви хочете змінити, що таке об'єкт:

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

Після виклику DoSomething tне посилається на оригінал new TestRef, а посилається на зовсім інший об’єкт.

Це також може бути корисно, якщо ви хочете змінити значення незмінного об'єкта, наприклад, a string. Ви не можете змінити значення одного stringразу, коли воно було створене. Але, використовуючи a ref, ви можете створити функцію, яка змінює рядок на іншу, яка має інше значення.

Редагувати: Як уже згадували інші. Використовувати це не дуже добре, refякщо це не потрібно. Використання refдає методу свободу змінювати аргумент на щось інше, абонентів методу потрібно буде кодувати, щоб переконатися, що вони обробляють цю можливість.

Також, коли тип параметра є об'єктом, то змінні об'єкта завжди виступають як посилання на об'єкт. Це означає, що коли використовується refключове слово, ви отримуєте посилання на посилання. Це дозволяє робити такі дії, як описано в прикладі, наведеному вище. Але, коли тип параметра є примітивним значенням (наприклад int), тоді, якщо цей параметр буде призначений у межах методу, значення аргументу, який було передано, буде змінено після повернення методу:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}

88

Вам потрібно розрізняти "передача посилання за значенням" і "передача параметра / аргументу за посиланням".

Я написав досить довгу статтю на цю тему, щоб уникнути необхідності ретельно писати щоразу, коли це з’являється на групах новин :)


1
Ну я зіткнувся з проблемою під час оновлення VB6 до .Net C # коду. Є підписи функцій / методів, які приймають параметри ref, out та plain. Тож як ми можемо краще розрізнити різницю між простою парам проти реф?
bonCodigo

2
@bonCodigo: Не впевнений, що ви маєте на увазі під «кращим розрізненням» - це частина підпису, і ви також повинні вказати refна сайті виклику ... де ще ви хочете, щоб це вирізняли? Семантика також досить чітка, але їх потрібно викладати обережно (а не "об'єкти передаються посиланням", що є загальним надмірним спрощенням).
Джон Скіт

я не знаю, чому візуальна студія все ще не показує явно те, що передано
MonsterMMORPG

3
@MonsterMMORPG: Я не знаю, що ти маєш на увазі під цим, боюся.
Джон Скіт

57

У .NET при передачі будь-якого параметра методу створюється копія. У типах значень означає, що будь-яка зміна, яку ви вносите в значення, знаходиться в області методу, і втрачається при виході з методу.

При передачі еталонного типу також робиться копія, але це копія посилання, тобто тепер у вас є ДВА посилання в пам'яті на один і той же об'єкт. Отже, якщо ви використовуєте посилання для модифікації об'єкта, він змінюється. Але якщо ви модифікуєте саме посилання - ми маємо пам’ятати, що це копія, - будь-які зміни також втрачаються після виходу з методу.

Як люди говорили раніше, присвоєння є модифікацією посилання, таким чином втрачається:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

Наведені вище методи не змінюють вихідний об'єкт.

Невелика модифікація вашого прикладу

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }

6
Мені подобається ця відповідь краще, ніж прийнята відповідь. Він більш чітко пояснює, що відбувається при передачі змінної типу посилання з ключовим словом ref. Дякую!
Стефан

17

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


16

З refвами можна написати:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

І t буде змінено після завершення методу.


8

Подумайте про змінні (наприклад foo) посилальних типів (наприклад List<T>) як ідентифікатори, що містять об'єкти форми "Об'єкт №24601". Припустимо, що вислів foo = new List<int> {1,5,7,9};викликає fooвміст "Object # 24601" (список з чотирма елементами). Тоді дзвінок foo.Lengthзапитає Об'єкт №24601 про його довжину, і він відповість 4, значить foo.Length, дорівнює 4.

Якщо fooвін передається методу без використання ref, він може внести зміни до Об'єкту №24601. Як наслідок таких змін, foo.Lengthможливо, більше не буде рівний 4. Сам метод, однак, не зможе змінити foo, який продовжує містити "Об'єкт №24601".

Передача fooяк refпараметр дозволить викликаному методу внести зміни не лише до Об'єкту №24601, але і до fooсамого себе. Метод може створити новий об’єкт №8675309 і зберегти посилання на нього в foo. Якщо це зробить так, fooбільше не буде "Об'єкт №24601", а натомість "Об'єкт №8675309".

На практиці змінні типу посилань не містять рядки форми "Об'єкт №8675309"; вони навіть не містять нічого, що може значимо перетворитись на число. Незважаючи на те, що кожна змінна тип опорного типу буде містити деякий бітовий візерунок, немає фіксованого зв'язку між бітовими шаблонами, що зберігаються в таких змінних, і об'єктами, які вони ідентифікують. Не існує способу коду, який міг би отримати інформацію з об'єкта чи посилання на нього, а пізніше визначити, чи інша посилання ідентифікувала цей самий об'єкт, якщо код не містив або не знав посилання, що ідентифікувало вихідний об'єкт.


5

Це подібно до передачі покажчика на вказівник у C. В .NET це дозволить вам змінити те, на що посилається оригінал T, особисто я думаю, якщо ви робите це в .NET, у вас, ймовірно, виникає проблема дизайну!


3

Використовуючи refключове слово з еталонними типами, ви фактично передаєте посилання на посилання. Багато в чому це те саме, що використовувати outключове слово, але з незначною різницею, що немає гарантії, що метод насправді присвоїть refпараметр 'ed.


3

ref імітує (або поводиться) як глобальну область лише для двох областей:

  • Абонент
  • Callee

1

Якщо ви передаєте значення, все-таки інакше. Ви можете змусити значення передаватись за посиланням. Це дозволяє, наприклад, передати ціле число до методу, а також метод змінити ціле число від вашого імені.


4
Незалежно від того, передаєте ви посилання або значення типу, поведінка за замовчуванням - це передавати значення. Вам просто потрібно зрозуміти, що для типів посилань значення, яке ви передаєте, є посиланням. Це не те ж саме, проходячи по посиланню.
Джон Скіт

1

Ref позначає, чи може функція дістати руки до самого об'єкта чи лише до його значення.

Проходження посиланням не пов'язане з мовою; це стратегія прив'язки параметрів поруч із проходом за значенням, пройти по імені, пройти за потребою тощо ...

Сторінка: назва класу TestRef- жахливо поганий вибір у цьому контексті;).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.