Чому C # не підтримує повернення посилань?


141

Я читав, що .NET підтримує повернення посилань, але C # ні. Чи є особлива причина? Чому я не можу зробити щось на кшталт:

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

8
гм ... цитування, будь ласка?
RPM1984

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

Я говорю про щось на кшталтreturn ref x
Том Сардуй

2
Через 4 роки ви можете зробити це саме за допомогою C# 7:)
Arghya C

Відповіді:


188

Це питання було предметом мого блогу 23 червня 2011 року . Дякую за чудове запитання!

Команда C # розглядає це для C # 7. Детальну інформацію див. На https://github.com/dotnet/roslyn/isissue/5233 .

ОНОВЛЕННЯ: Ця функція перетворила його на C # 7!


Ви праві; .NET підтримує методи підтримки, які повертають керовані посилання на змінні. .NET також підтримує локальні змінні, які містять керовані посилання на інші змінні. (Однак зауважте, що .NET не підтримує поля чи масиви, які містять керовані посилання на інші змінні, оскільки це надто ускладнює історію збору сміття. Також типи "керована посилання на змінну" не можуть бути конвертовані в об'єкт , і тому не можуть використовуватися як введіть аргументи на загальні типи чи методи.)

Коментер "RPM1984" чомусь попросив цитувати цей факт. RPM1984 Я рекомендую прочитати специфікацію розділу CLI розділу 8.2.1.1 "Керовані покажчики та пов'язані типи" для отримання інформації про цю особливість .NET.

Цілком можливо створити версію C #, яка підтримує обидві ці функції. Потім ви могли робити такі речі

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

а потім зателефонуйте за допомогою

int a = 123;
int b = 456; 
ref int c = ref Max(ref a, ref b); 
c += 100;
Console.WriteLine(b); // 556!

Я емпірично знаю, що можна створити версію C #, яка підтримує ці функції, тому що я це зробив . Просунуті програмісти, зокрема люди, що переносять некерований код C ++, часто просять у нас більше C ++-подібної здатності робити речі з посиланнями, не виймаючи з себе великого молотка фактично за допомогою покажчиків та закріплення пам’яті повсюди. Використовуючи керовані довідки, ви отримуєте ці переваги, не платячи витрати на викручування вашого сміття.

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

Крім того, правильне його виконання вимагало б деяких змін до CLR. Наразі CLR розглядає методи повернення як правові, але неперевірені, оскільки у нас немає детектора, який би визначав цю ситуацію:

ref int M1(ref int x)
{
    return ref x;
}

ref int M2()
{
    int y = 123;
    return ref M1(ref y); // Trouble!
}

int M3()
{
    ref int z = ref M2();
    return z;
}

M3 повертає вміст локальної змінної M2, але термін експлуатації цієї змінної закінчився! Можна записати детектор, який визначає використання повторних повернень, які явно не порушують безпеку стека. Що ми робимо, - це написати такий детектор, і якщо детектор не зміг би довести безпеку стека, то ми не дозволили б використання повернення ref у цій частині програми. Це не велика кількість розробок для роботи, але це велике навантаження на тестувальні групи, щоб переконатися, що у нас справді всі справи. Це просто інша річ, яка збільшує вартість функції до того моменту, коли зараз переваги не переважають за витратами.

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

(Дивись також питання , пов'язані з Чи можливо це повернути посилання на змінну в C #? І Можу чи я використовувати посилання всередині C # функція як C ++? )


4
@EricLippert: У мене немає переконливого прикладу - це просто те, що мені було цікаво. Відмінна та переконлива відповідь
Том Сардуй

1
@Eric: У твоєму прикладі не було б більш придатним залишатись y живим після повернення з M2? Я б очікував, що ця функція може працювати так, як лямбда, що захоплюють місцевих жителів. Або поведінка, яку ви запропонували, тому що це, як CLR обробляє цей сценарій?
Fede

3
@Eric: можливість IMHO мати властивості повертати посилання на типи значень є головним недоліком у мовах .net. Якщо Arr - це масив типу значень (наприклад, Point), можна сказати, наприклад, Arr (3) .X = 9 і знати, що не змінилося значення Arr (9) .X або навіть SomeOtherArray (2). X; якби замість Arr був масив якогось еталонного типу, таких гарантій не існувало б. Те, що оператор індексації масиву повертає посилання, є надзвичайно корисним; Я вважаю дуже прикро, що жоден інший тип колекції не може надати таку функціональність.
supercat

4
Еріку, який найкращий спосіб надати зворотній зв'язок щодо сценаріїв (як ви пропонуєте в останньому абзаці), щоб максимізувати шанси, що їх бачать? MS Connect? UserVoice (з його довільним обмеженням на 10 пост / голосування)? Щось ще?
Роман Старков

1
@ThunderGr: Це філософія C # для "небезпечної" - якщо ви пишете код, який, можливо, небезпечний для пам'яті, тоді C # наполягає на тому, щоб ви позначили його як "небезпечний", щоб ви відповідали за безпеку пам'яті. У C # вже є небезпечна версія функції, за умови, що змінна, про яку йде мова, не має керованого типу. Питання полягає в тому, чи повинна команда C # скласти небезпечну версію, яка обробляє керовані типи. Якщо це спрощує розробник писати жахливі помилки, це не відбудеться. C # не є C ++, мовою, яка дозволяє легко писати жахливі помилки. C # є безпечним дизайном.
Ерік Ліпперт

21

Ви говорите про методи, які повертають посилання на тип значення. Єдиний вбудований приклад в C #, про який я знаю, - це масив-аксесуар типу значення:

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

а тепер створіть масив цієї структури:

var points = new Point[10];
points[0].X = 1;
points[0].Y = 2;

У цьому випадку індексаторpoints[0] масиву повертає посилання на struct. Неможливо написати власний індексатор (наприклад, для спеціальної колекції), який має таку саму поведінку "повернути посилання".

Я не розробив мову C #, тому не знаю всіх міркувань, які не підтримують її, але я думаю, що коротка відповідь може бути: ми без цього добре обійдемось.


8
Том запитує про методи, які повертають посилання на змінну . Змінна не повинна мати типовий тип, хоча, звичайно, це зазвичай те, що люди хочуть, коли вони хочуть методів повернення. Інакше чудовий аналіз; ви вірні, що єдине місце на мові C #, де складний вираз створює посилання на змінну, якою користувач може потім маніпулювати, - це індексатор масиву. (І звичайно, учасник оператора доступу "" "між приймачем і полем, але це, очевидно, доступ до змінної.)
Ерік Ліпперт,

1

Ви завжди можете зробити щось на кшталт:

public delegate void MyByRefConsumer<T>(ref T val);

public void DoSomethingWithValueType(MyByRefConsumer<int> c)
{
        int x = 2;
        c(ref x);
        //Handle potentially changed x...
}

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