Dlaczego C # nie obsługuje zwrotu odwołań?

141

Przeczytałem, że .NET obsługuje zwracanie odwołań, ale C # tego nie robi. Czy jest jakiś szczególny powód? Dlaczego nie mogę zrobić czegoś takiego:

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 
Tom Sarduy
źródło
8
um ... proszę o cytat?
RPM1984,
5
C # ma typy wartości i odwołań i oba mogą zostać zwrócone, co masz na myśli.
powtórz
Mówię o czymś takim jakreturn ref x
Tom Sarduy,
2
Po 4 latach możesz to zrobić dokładnie z C# 7:)
Arghya C

Odpowiedzi:

188

To pytanie było tematem mojego bloga 23 czerwca 2011 roku . Dzięki za świetne pytanie!

Zespół C # rozważa to dla języka C # 7. Zobacz https://github.com/dotnet/roslyn/issues/5233, aby uzyskać szczegółowe informacje.

AKTUALIZACJA: Funkcja została wprowadzona do C # 7!


Masz rację; NET obsługuje metody, które zwracają zarządzane odwołania do zmiennych. NET obsługuje również zmienne lokalne, które zawierają zarządzane odwołania do innych zmiennych. (Należy jednak pamiętać, że .NET nie obsługuje pól ani tablic, które zawierają zarządzane odwołania do innych zmiennych, ponieważ to nadmiernie komplikuje historię czyszczenia pamięci. Również typy „zarządzanych odwołań do zmiennych” niekonwertowane na obiekt i dlatego nie mogą być używane jako wpisz argumenty na typy ogólne lub metody).

Komentator „RPM1984” z jakiegoś powodu poprosił o przytoczenie tego faktu. RPM1984 Zachęcam do przeczytania specyfikacji CLI Partition I Section 8.2.1.1, "Managed pointers and related types", aby uzyskać informacje na temat tej funkcji .NET.

Całkowicie możliwe jest utworzenie wersji C #, która obsługuje obie te funkcje. Możesz wtedy robić takie rzeczy jak

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

a następnie zadzwoń za pomocą

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

Wiem empirycznie, że jest możliwe zbudowanie wersji C #, która obsługuje te funkcje, ponieważ tak zrobiłem . Zaawansowani programiści, szczególnie osoby przenoszące niezarządzany kod C ++, często proszą nas o więcej możliwości, takich jak C ++, robienia rzeczy z referencjami bez konieczności wyciągania wielkiego młota związanego z faktycznym używaniem wskaźników i przypinaniem pamięci w dowolnym miejscu. Korzystając z referencji zarządzanych, uzyskujesz te korzyści bez ponoszenia kosztów związanych z pogorszeniem wydajności usuwania elementów bezużytecznych.

Rozważaliśmy tę funkcję i faktycznie wdrożyliśmy ją na tyle, aby pokazać ją innym zespołom wewnętrznym w celu uzyskania ich opinii. Jednak w tej chwili, na podstawie naszych badań, uważamy, że ta funkcja nie ma wystarczająco szerokiego odwołania lub przekonujących przypadków użycia, aby stała się prawdziwą obsługiwaną funkcją językową . Mamy inne, wyższe priorytety oraz ograniczoną ilość czasu i wysiłku, więc nie zamierzamy robić tej funkcji w najbliższym czasie.

Również wykonanie tego poprawnie wymagałoby pewnych zmian w CLR. W tej chwili CLR traktuje metody powrotu jako legalne, ale nieweryfikowalne, ponieważ nie mamy detektora, który wykrywa tę sytuację:

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 zwraca zawartość zmiennej lokalnej M2, ale czas życia tej zmiennej dobiegł końca! Możliwe jest napisanie detektora, który określa użycie zwrotów ref, które wyraźnie nie naruszają bezpieczeństwa stosu. To co byśmy zrobili, to napisanie takiego detektora, a gdyby detektor nie potrafił udowodnić bezpieczeństwa stosu, to nie pozwolilibyśmy na użycie zwrotów ref w tej części programu. Nie jest to wielka praca deweloperów, ale jest dużym obciążeniem dla zespołów testujących, aby upewnić się, że naprawdę mamy wszystkie przypadki. To tylko kolejna rzecz, która podnosi koszt funkcji do tego stopnia, że ​​w tej chwili korzyści nie przeważają nad kosztami.

Jeśli możesz mi opisać, dlaczego potrzebujesz tej funkcji, byłbym naprawdę wdzięczny . Im więcej mamy informacji od prawdziwych klientów o tym, dlaczego tego chcą, tym większe jest prawdopodobieństwo, że kiedyś trafi do produktu. To urocza mała funkcja i chciałbym móc ją w jakiś sposób przekazać klientom, jeśli jest wystarczające zainteresowanie.

(Patrz również pytania związane jest to możliwe do zwróci referencję do zmiennej w C #? I Czy mogę użyć odwołania wewnątrz funkcji C # jak C ++? )

Eric Lippert
źródło
4
@EricLippert: Nie mam przekonującego przykładu, to tylko coś, nad czym się zastanawiałem. Znakomita i przekonująca odpowiedź
Tom Sarduy
1
@Eric: Na przykład, czy nie byłby bardziej odpowiedni do utrzymania się y przy życiu po powrocie z M2? Spodziewałbym się, że ta funkcja będzie działać jak lambdy przechwytujące lokalne. A może jest to zachowanie, które zaproponowałeś, ponieważ jest to sposób, w jaki środowisko CLR obsługuje ten scenariusz?
Fede
3
@Eric: IMHO możliwość zwracania przez właściwości odwołań do typów wartości jest głównym zaniedbaniem w językach .net. Jeśli Arr jest tablicą typu wartości (np. Point), można powiedzieć np. Arr (3) .X = 9 i wiedzieć, że nie zmieniono wartości Arr (9) .X lub nawet SomeOtherArray (2). X; gdyby Arr był tablicą pewnego typu referencyjnego, nie istniałyby żadne takie gwarancje. Fakt, że operator indeksowania tablicy zwraca odwołanie, jest niezwykle przydatny; Uważam za wysoce niefortunne, że żaden inny rodzaj kolekcji nie zapewnia takiej funkcjonalności.
supercat
4
Eric, jaki jest najlepszy sposób na przekazanie opinii na temat scenariuszy (jak sugerujesz w ostatnim akapicie), aby zmaksymalizować szanse, że zostanie to zauważone? MS Connect? UserVoice (z dowolnym limitem 10 postów / głosów)? Coś innego?
Roman Starkov
1
@ThunderGr: To jest filozofia C # dotycząca „niebezpiecznego” - jeśli piszesz kod, który prawdopodobnie nie jest bezpieczny dla pamięci, C # nalega, aby oznaczyć go jako „niebezpieczny”, abyś wziął odpowiedzialność za bezpieczeństwo pamięci. C # ma już niebezpieczną wersję funkcji, pod warunkiem, że dana zmienna jest typu niezarządzanego. Pytanie brzmi, czy zespół C # powinien utworzyć niebezpieczną wersję, która obsługuje typy zarządzane. Jeśli to ułatwi programiście pisanie okropnych błędów, to się nie wydarzy. C # to nie C ++, język, który ułatwia pisanie okropnych błędów. C # jest bezpieczny z założenia.
Eric Lippert,
21

Mówisz o metodach, które zwracają odniesienie do typu wartości. Jedyny wbudowany przykład w C #, o którym wiem, to metoda dostępu do tablicy typu wartości:

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

a teraz utwórz tablicę o tej strukturze:

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

W tym przypadku indeksatorpoints[0] tablicy zwraca odwołanie do struktury. Niemożliwe jest napisanie własnego indeksatora (na przykład dla kolekcji niestandardowej), który ma takie samo zachowanie „zwraca odwołanie”.

Nie zaprojektowałem języka C #, więc nie znam wszystkich powodów, dla których go nie obsługuję, ale myślę, że krótka odpowiedź może brzmieć: bez niego możemy sobie dobrze radzić.

Rick Sladkey
źródło
8
Tom pyta o metody, które zwracają odniesienie do zmiennej . Zmienna nie musi być typu wartościowego, chociaż oczywiście tego właśnie chcą ludzie, gdy chcą metody zwracające ref. W przeciwnym razie świetna analiza; masz rację, że jedynym miejscem w języku C #, w którym złożone wyrażenie tworzy odwołanie do zmiennej, którą użytkownik może następnie manipulować, jest indeksator tablicy. (I oczywiście operator dostępu do elementu „.” Między odbiornikiem a polem, ale jest to dość oczywisty dostęp do zmiennej.)
Eric Lippert,
1

Zawsze możesz zrobić coś takiego:

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

public void DoSomethingWithValueType(MyByRefConsumer<int> c)
{
        int x = 2;
        c(ref x);
        //Handle potentially changed x...
}
Eladian
źródło
1

C # 7.0 obsługuje zwracanie odwołań. Zobacz moją odpowiedź tutaj .

KodowanieYoshi
źródło