Коли використовувати ref і коли це не потрібно в C #


104

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

byte[] received_s = new byte[2048];
IPEndPoint tmpIpEndPoint = new IPEndPoint(IPAddress.Any, UdpPort_msg);
EndPoint remoteEP = (tmpIpEndPoint);

int sz = soUdp_msg.ReceiveFrom(received_s, ref remoteEP); 

Це мене бентежить, тому що і те, received_sі remoteEPповертають речі з функції. Для чого remoteEPпотрібен refа received_sчи ні?

Я також програміст змінного струму, тому у мене виникають проблеми з виведенням вказівників з голови.

Редагувати: Схоже, що об'єкти в C # є вказівниками на об'єкт під кришкою. Отже, коли ви передаєте об'єкт функції, ви можете змінювати вміст об'єкта через покажчик, і єдине, що передається функції, - це вказівник на об'єкт, тому сам об'єкт не копіюється. Ви використовуєте ref або out, якщо хочете мати можливість вимкнути або створити новий об'єкт у функції, яка є як подвійний вказівник.

Відповіді:


216

Коротка відповідь: прочитайте мою статтю про передачу аргументів .

Довга відповідь: коли параметр типу посилання передається за значенням, передається тільки посилання, а не копія об'єкта. Це як передача вказівника (за значенням) на C або C ++. Зміни в вартості самого параметра не буде видно викликає, але зміни в об'єкті якої опорні точки , щоб буде видно.

Коли параметр (будь-якого виду) передається з допомогою посилання, це означає , що будь-які зміни параметра помічені викликає - зміни параметра є зміни в змінної.

Стаття пояснює все це більш докладно, звичайно :)

Корисна відповідь: вам майже ніколи не потрібно використовувати перемикання / вимкнення . Це, в основному, спосіб отримати ще одне повернене значення, і його, як правило, слід уникати саме тому, що це означає, що метод, ймовірно, намагається зробити занадто багато. Це не завжди так ( TryParseі т. Д. - канонічні приклади розумного використання out), але використання відсилки / виходу повинно бути відносно рідкістю.


38
Я думаю, у вас змішана коротка відповідь і довга відповідь; це велика стаття!
Програматор поза законом

23
@Outlaw: Так, але сама коротка відповідь, директива на читання статті - лише 6 слів :)
Jon Skeet

27
Довідка! :)
gonzobrains

5
@Liam Використання ref, як ви, може, для вас зрозуміліше, але це насправді може заплутати інших програмістів (тих, хто все одно знає, що це ключове слово), оскільки ви, по суті, говорите потенційним абонентам: "Я можу змінити змінну, яку ви використовується в методі виклику, тобто перепризначте його іншому об'єкту (або навіть нульовому, якщо можливо), тому не зациклюйтесь на ньому і не переконайтесь, що ви його підтвердили, коли я закінчую його ". Це досить сильно і абсолютно відрізняється від "цей об’єкт може бути змінено", що завжди має місце в будь-який час, коли ви передаєте посилання на об'єкт як параметр.
mbargiel

1
@ M.Mimpen: C # 7 дозволить (сподіваємось)if (int.TryParse(text, out int value)) { ... use value here ... }
Джон Скіт

26

Подумайте про параметр non ref, як на вказівник, а параметр ref як на подвійний покажчик. Це мені найбільше допомогло.

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

Для кількох повернених значень

  • Створіть структури, які представляють кілька повернених значень

Для примітивів, які змінюються в результаті в результаті виклику методу (метод має побічні ефекти на примітивні параметри)

  • Реалізація методу в об'єкті як метод екземпляра та маніпулювання станом об'єкта (а не параметрами) як частини виклику методу
  • Скористайтеся рішенням із множинним поверненням і об'єднайте повернені значення зі своїм станом
  • Створіть об’єкт, який містить стан, яким можна керувати методом, і передайте цей об'єкт як параметр, а не самі примітиви.

8
Добрий Бог. Я мав би прочитати це 20x, щоб зрозуміти це. Мені здається купа зайвої роботи, щоб зробити щось просте.
PositiveGuy

.NET Framework не відповідає вашому # 1 правилу. ("Для кількох повернених значень створюйте структури"). Візьмемо для прикладу IPAddress.TryParse(string, out IPAddress).
Swen Kooij

@SwenKooij Ви маєте рацію. Я припускаю, що в більшості місць, де вони використовують параметри, це або (а) вони завершують API Win32, або (b) це були перші дні, і програмісти C ++ приймали рішення.
Майкл Медоуз

@SwenKooij Я знаю, що ця відповідь запізнюється на багато років, але це є. Ми звикли до TryParse, але це не означає, що це добре. Було б краще, ніж, ніж у if (int.TryParse("123", out var theInt) { /* use theInt */ }нас, var candidate = int.TrialParse("123"); if (candidate.Parsed) { /* do something with candidate.Value */ }це більше коду, але набагато більше відповідає дизайну мови C #.
Майкл Медоуз

9

Ви, ймовірно, можете написати цілий додаток C # і ніколи не передавати об'єкти / структури за посиланням.

У мене був професор, який мені це сказав:

Єдине місце, де ви б використовували рефлекси, це те, де ви:

  1. Хочете передати великий об'єкт (тобто об'єкти / структура має об'єкти / структури всередині нього на декілька рівнів), і копіювати його було б дорого і
  2. Ви викликаєте Framework, API API або інший API, який цього вимагає.

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

Я погоджуюся з його порадою, і за мої п’ять плюс років, починаючи зі школи, у мене ніколи не було потреби в тому, щоб закликати Framework або API API.


3
Якщо ви плануєте реалізувати "Swap", проходження повного переліку може бути корисним.
Брайан

@Chris це створить якісь проблеми, якщо я буду використовувати ключове слово ref для невеликих об'єктів?
ManirajSS

@TechnikEmpire "Але зміни об'єкта в межах виклику функції не відбиваються на абонента." Це неправильно. Якщо я передам Особу SetName(person, "John Doe"), властивість імені зміниться, і ця зміна відобразиться на абонента.
М. Мімпен

@ M.Mimpen Коментар видалено. Я ледве взяв C # у той момент і чітко розмовляв зі своїм * $$. Дякую за те, що ви звернули увагу на мене

@Chris - Я впевнений, що передача "великого" об'єкта не дорога. Якщо ви просто передаєте його за значенням, ви все одно просто передаєте один покажчик, чи не так?
Ян

3

Оскільки получен_с є масивом, ви передаєте вказівник на цей масив. Функція маніпулює наявними даними на місці, не змінюючи базове місце чи покажчик. Ключове слово ref означає, що ви передаєте фактичний вказівник на місце розташування та оновлюєте цей покажчик у зовнішній функції, тому значення в зовнішній функції зміниться.

Наприклад, байтовий масив - це вказівник на одну і ту ж пам’ять до і після, пам'ять щойно оновлена.

Посилання на кінцеву точку фактично оновлює вказівник на Кінцеву точку у зовнішній функції до нового екземпляра, згенерованого всередині функції.


3

Подумайте про перелік як про те, що ви передаєте вказівник за посиланням. Якщо ви не використовуєте ref, ви передаєте вказівник за значенням.

А ще краще ігноруйте те, що я щойно сказав (це, мабуть, вводить в оману, особливо з типами значень) і читайте цю сторінку MSDN .


Власне, неправда. Принаймні друга частина. Будь-який тип посилань завжди передаватиметься посиланням, використовуєте ви чи ні.
Ерік Функенбуш

Насправді, при подальшому роздумі, це не вийшло правильно. Посилання фактично передається за значенням без типу ref. Значення, зміна значень, на які вказує посилання, змінює вихідні дані, але сама зміна посилання не змінює вихідну посилання.
Ерік Функенбуш

2
Нерефлекторний тип посилання не передається посиланням. Посилання на тип посилання передається за значенням. Але якщо ви хочете розглядати посилання як вказівник на те, на що посилається, те, що я сказав, має сенс (але думати таким чином може бути оманливим). Звідси моє попередження.
Брайан

Посилання мертве - спробуйте це замість цього - docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
ДАЙТЕ МЕ-

0

моє розуміння полягає в тому, що всі об'єкти, похідні від класу Object, передаються як покажчики, тоді як звичайні типи (int, struct) не передаються як покажчики і потребують посилання. Я не впевнений у рядку (це в кінцевому підсумку походить від класу Object?)


Це могло б використовувати деяке уточнення. Яка різниця між ціннісними та референтними тио. Відповідь, чому це стосується використання ключового слова ref для параметра?
oɔɯǝɹ

У .net все, крім вказівників, параметрів типу та інтерфейсів, походить від Object. Важливо розуміти, що "звичайні" типи (правильно названі "типи значень") успадковують і від об'єкта. Ви вірно з цього питання: типи значень (за замовчуванням) передаються за значенням, тоді як типи посилань передаються за посиланням. Якщо ви хочете змінити тип значення, а не повертати новий (обробляти його як тип посилання всередині методу), вам доведеться використовувати ключове слово ref на ньому. Але це поганий стиль, і ви просто не повинні цього робити, якщо ви абсолютно не впевнені, що вам доведеться :)
buddybubble

1
щоб відповісти на ваше запитання: рядок є похідним від object.It - це тип посилання, який веде себе як тип значення (з точки зору продуктивності та логічних причин)
buddybubble

0

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

Це дійсно корисно лише тоді, коли зусилля з оптимізації докладаються до крайніх рівнів, приносячи в жертву читабельність і, можливо, передбачуваність та ремонтопридатність для економії мілісекунд або, можливо, розділених мілісекунд.


-1

Спочатку нульове правило, примітиви передаються за значенням (стек) і Непримітивні за посиланням (Heap) в контексті TYPES.

Задіяні параметри передаються значенням за замовчуванням. Хороший пост, який детально пояснює речі. http://yoda.arachsys.com/csharp/parameters.html

Student myStudent = new Student {Name="A",RollNo=1};

ChangeName(myStudent);

static void ChangeName(Student s1)
{
  s1.Name = "Z"; // myStudent.Name will also change from A to Z
                // {AS s1 and myStudent both refers to same Heap(Memory)
                //Student being the non-Primitive type
}

ChangeNameVersion2(ref myStudent);
static void ChangeNameVersion2(ref Student s1)
{
  s1.Name = "Z"; // Not any difference {same as **ChangeName**}
}

static void ChangeNameVersion3(ref Student s1)
{
    s1 = new Student{Name="Champ"};

    // reference(myStudent) will also point toward this new Object having new memory
    // previous mystudent memory will be released as it is not pointed by any object
}

Можна сказати (з попередженням) Non-примітивні типи нічого , крім покажчиків І коли ми передаємо їх вих ми можемо сказати , що ми проходимо Подвійний покажчик


Усі параметри передаються за значенням за замовчуванням у C #. Будь-який параметр може передаватися посиланням. Для типів посилань значення, яке передається (або за посиланням, або за значенням), є самим посиланням. Це абсолютно не залежить від того, як він передається.
Сервіс

Погодьтесь @Servy! "Коли ми чуємо використані слова" посилання "або" значення ", нам слід дуже чітко пам'ятати, чи маємо на увазі, що параметр є еталонним або значенням параметру, чи маємо на увазі, що тип, що стосується, є посиланням або типом значення" Малий Плутанина там, на моєму боці, коли я сказав, що спершу правило нульового ґрунту, примітиви передаються за значенням (стек) та Непримітивні за посиланням (Heap), які я мав на увазі від залучених типів, а не параметрів! Коли ми говоримо про параметри, ви правильні , Усі параметри передаються за значенням, за замовчуванням в C #.
Surender Singh Malik,

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

1
Ви передаєте параметри за посиланням або за значенням. Терміни "за посиланням" та "за значенням" не використовуються для опису, є тип типу значенням або еталонним типом.
Сервіс

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