Jaka jest różnica między typem referencyjnym a typem wartości w języku C #?

100

Jakiś facet zadał mi to pytanie kilka miesięcy temu i nie mogłem szczegółowo tego wyjaśnić. Jaka jest różnica między typem referencyjnym a typem wartości w C #?

Wiem, że typy wartości są int, bool, float, etc i referencyjne typy delegate, interfaceitp Albo jest to złe, też?

Czy możesz mi to wyjaśnić w profesjonalny sposób?

tugberk
źródło
3
Krótko mówiąc, myślę, że pytanie jest zadawane o C #, ale w rzeczywistości chodzi o C # + .NET. Nie można analizować języka C # bez analizy .NET. Nie będę ponownie tagował pytania, ponieważ można by było zwrócić uwagę na analizę jednego bez analizowania drugiego (iteratory i zamknięcia, patrzę na ciebie)
xanatos
@xanatos jest to najodpowiedniejsze pytanie o CLI, które C #, VB.Net i Net mają wspólnego. Powinien istnieć tag dla CLI, ale CLI jest traktowany jako coś innego. Istnieje CLR, ale to jest implementacja, a nie standard.
user34660

Odpowiedzi:

172

Twoje przykłady są trochę dziwne, ponieważ podczas int, booli floatsą specyficzne typy, interfejsów i delegaci są rodzaje typu - tak jak structi enumsą rodzaje typów wartości.

Pisałem o wyjaśnienie rodzajów i typów wartości odniesienia w tym artykule . Z przyjemnością omówię wszystkie fragmenty, które mogą być mylące.

Wersja „TL; DR” polega na przemyśleniu wartości zmiennej / wyrażenia określonego typu. W przypadku typu wartości wartością jest sama informacja. W przypadku typu referencyjnego wartość jest odwołaniem, które może być zerowe lub może być sposobem nawigacji do obiektu zawierającego informacje.

Na przykład pomyśl o zmiennej jak o kartce papieru. Mógłby mieć wpisaną wartość „5” lub „false”, ale nie mógł mieć mojego domu… ​​musiałby mieć wskazówki dojazdu do mojego domu. Te kierunki są odpowiednikiem odniesienia. W szczególności dwie osoby mogą mieć różne kartki papieru zawierające te same wskazówki do mojego domu - a jeśli jedna osoba postąpiłaby zgodnie z tymi wskazówkami i pomalowała mój dom na czerwono, druga osoba również zauważyłaby tę zmianę. Gdyby obaj mieli po prostu oddzielne zdjęcia mojego domu na papierze, jedna osoba kolorująca papier w ogóle nie zmieniłaby papieru drugiej osoby.

Jon Skeet
źródło
2
Należy zauważyć, że istnieją trzy różne podstawowe typy semantyki, które coś może zaoferować: niezmienna semantyka, zmienna semantyka wartości i zmienna semantyka odwołań. Koncepcyjnie rodzaj semantyki, którą rzecz implementuje, jest ortogonalny w stosunku do tego, czy jest przechowywany jako samodzielny obiekt sterty, czy zmienna / pole (struktura). W praktyce, podczas gdy struktury, które nie ujawniają swoich pól, mogą implementować dowolny rodzaj semantyki, fakt, że .net pozwala na swobodne udostępnianie odwołań do sterty, oznacza, że ​​obiekty sterty nie mogą implementować semantyki wartości zmiennej.
supercat
Nie dostałem tego kawałka - while int, bool and float are specific types, interfaces and delegates are kinds of type - just like struct and enum are kinds of value types. Co masz na myśli, mówiąc int, bool to określone typy? Wszystko w C # np. Int, bool, float, class, interface, delegate jest typem (dokładniej typem danych). Typy danych są segregowane jako „Typ odwołania” i „Typ wartości” w języku C #. W takim razie dlaczego mówisz, że int jest określonym typem, a interfejs jest rodzajem typu?
RBT
2
@RBT: Typy danych nie są podzielone tylko na „typ referencyjny” i „typ wartości”. Są również podzielone na „klasę, strukturę, wyliczenie, delegata, interfejs”. intjest strukturą, stringjest klasą, Actionjest delegatem itp. Twoja lista „int, bool, float, class, interface, delegate” jest listą zawierającą różne rodzaje rzeczy, tak samo jak „10, int” jest lista zawierająca różne rodzaje rzeczy.
Jon Skeet
@JonSkeet Być może odpowiedź w tym poście jest więc nieco myląca.
RBT
@RBT: Powiedziałbym, że to trochę źle sformułowane, ale nie okropne.
Jon Skeet
26

Typ wartości:

Przechowuje jakąś wartość, a nie adresy pamięci

Przykład:

Struct

Przechowywanie:

TL; DR : Wartość zmiennej jest przechowywana wszędzie tam, gdzie jest ona zdeklarowana. Na przykład zmienne lokalne znajdują się na stosie, ale zadeklarowane wewnątrz klasy jako element członkowski, pozostają na stercie ściśle powiązane z klasą, w której są zadeklarowane.
Dłuższe : w ten sposób typy wartości są przechowywane wszędzie tam, gdzie są zadeklarowane. Np .: intwartość a wewnątrz funkcji jako zmienna lokalna byłaby przechowywana na stosie, podczas gdy intwartość in zadeklarowana jako składowa w klasie byłaby przechowywana na stercie z klasą, w której jest zadeklarowana. Typ wartości na klasa ma typ życia, który jest dokładnie taki sam, jak klasa, w której została zadeklarowana, i nie wymaga prawie żadnej pracy od czyszczenia pamięci. Jest to jednak bardziej skomplikowane, odniosę się do książki @ JonSkeet „ C # In DepthPamięć w .NET ”dla bardziej zwięzłego wyjaśnienia.

Zalety:

Typ wartości nie wymaga dodatkowego wyrzucania elementów bezużytecznych. Pobiera śmieci zbierane wraz z instancją, w której żyje. Zmienne lokalne w metodach są czyszczone po opuszczeniu metody.

Wady:

  1. Gdy do metody przekazywany jest duży zestaw wartości, zmienna odbierająca faktycznie kopiuje, więc w pamięci znajdują się dwie nadmiarowe wartości.

  2. Ponieważ zajęcia są pomijane, traci wszystkie korzyści

Typ odniesienia:

Przechowuje adres pamięci o wartości, a nie wartości

Przykład:

Klasa

Przechowywanie:

Przechowywane na stercie

Zalety:

  1. Kiedy przekazujesz zmienną referencyjną do metody i zmienia się ona faktycznie zmienia oryginalną wartość, podczas gdy w typach wartości pobierana jest kopia danej zmiennej i ta wartość jest zmieniana.

  2. Gdy wielkość zmiennej jest większa, typ odniesienia jest dobry

  3. Ponieważ klasy są zmiennymi typu referencyjnego, dają możliwość ponownego wykorzystania, co jest korzystne dla programowania obiektowego

Wady:

Więcej pracy przy alokowaniu i usuwaniu odwołań podczas odczytywania przeciążenia value.extra dla modułu odśmiecania pamięci

Durai Amuthan.H
źródło
5
Niekoniecznie jest prawdą, że typy referencyjne są przechowywane na stercie, a typy wartości są przechowywane na stosie. Przeczytaj yoda.arachsys.com/csharp/memory.html, jeśli chcesz dowiedzieć się więcej.
Rhys,
1
W tej odpowiedzi jest wiele nieporozumień. Proszę przeczytać Jeff Richters CLR przez C #. Typy wartości są przechowywane w stosie wątków i nie podlegają wyrzucaniu elementów bezużytecznych (GC) - nie mają nic wspólnego z GC. Typy odwołań są przechowywane na zarządzanym stercie i dlatego podlegają GC. Jeśli typ odniesienia ma odniesienie do katalogu głównego, nie można go zebrać i jest promowany przez pokolenia, 0, 1 i 2. Jeśli nie ma odniesienia do katalogu głównego, może zostać zebrany w pamięci, a następnie przechodzi przez proces zwany Resurrection, gdzie zostaje zabity i przywrócony do życia, a następnie ostatecznie zebrany.
Jeremy Thompson
13

Okazało się, że łatwiej jest zrozumieć różnicę tych dwóch, jeśli wiesz, jak komputer alokuje rzeczy w pamięci i wiesz, czym jest wskaźnik.

Odniesienie jest zwykle powiązane ze wskaźnikiem. Oznacza to, że adres pamięci, w którym znajduje się twoja zmienna, faktycznie zawiera inny adres pamięci rzeczywistego obiektu w innej lokalizacji pamięci.

Przykład, który zamierzam podać, jest znacznie uproszczony, więc potraktuj go z przymrużeniem oka.

Wyobraź sobie, że pamięć komputera to zbiór skrytek pocztowych w rzędzie (zaczynając od PO Box 0001 do PO Box n), które mogą pomieścić coś w środku. Jeśli skrzynki pocztowe nie robią tego za Ciebie, wypróbuj tablicę haszującą lub słownik, tablicę lub coś podobnego.

Tak więc, gdy robisz coś takiego:

var a = "Witaj";

komputer wykona następujące czynności:

  1. alokuj pamięć (powiedzmy zaczynając od miejsca pamięci 1000 na 5 bajtów) i wstaw H (na 1000), e (na 1001), l (na 1002), l (na 1003) io (na 1004).
  2. alokować gdzieś w pamięci (powiedzmy w lokalizacji 0500) i przypisać go jako zmienną a.
    Więc to trochę jak alias (0500 to a).
  3. przypisz wartość w tej lokalizacji pamięci (0500) do 1000 (czyli tam, gdzie w pamięci zaczyna się ciąg Hello). W ten sposób zmienna a zawiera odniesienie do faktycznej początkowej lokalizacji w pamięci ciągu „Hello”.

Typ wartości będzie przechowywać rzeczywisty element w swojej lokalizacji pamięci.

Tak więc, gdy robisz coś takiego:

var a = 1;

komputer wykona następujące czynności:

  1. przydziel miejsce w pamięci powiedz o godzinie 0500 i przypisz je do zmiennej a (ten sam alias)
  2. umieść w nim wartość 1 (w komórce pamięci 0500).
    Zauważ, że nie przydzielamy dodatkowej pamięci do przechowywania rzeczywistej wartości (1). Zatem a faktycznie przechowuje rzeczywistą wartość i dlatego nazywa się typem wartości.
Jimmy Chandra
źródło
1
Możesz być zainteresowany blogs.msdn.com/b/ericlippert/archive/2009/02/17/…
Jon Skeet,
@Jon, Cóż, tego rodzaju unieważnienie tego, co mówiłem, LOL. Ale, jak powiedziałem, bardzo uproszczone jest zrozumienie między tymi dwoma typami, co w moim przypadku okazało się pomocne. Przynajmniej tak to sobie wyobrażałem :).
Jimmy Chandra,
8

To jest z mojego postu z innego forum, jakieś dwa lata temu. Chociaż językiem jest vb.net (w przeciwieństwie do C #), koncepcje typu wartości w porównaniu z typem referencyjnym są jednolite w całej sieci .NET, a przykłady nadal są aktualne.

Należy również pamiętać, że w .net WSZYSTKIE typy technicznie wywodzą się z typu podstawowego Object. Typy wartości są zaprojektowane tak, aby zachowywały się jako takie, ale ostatecznie dziedziczą również funkcjonalność typu podstawowego Object.

A. Typy wartości są po prostu tym - reprezentują odrębny obszar w pamięci, w którym przechowywana jest dyskretna WARTOŚĆ. Typy wartości mają stałą wielkość pamięci i są przechowywane w stosie, który jest zbiorem adresów o stałym rozmiarze.

Kiedy składasz takie oświadczenie:

Dim A as Integer
DIm B as Integer

A = 3
B = A 

Wykonałeś następujące czynności:

  1. Utworzono 2 spacje w pamięci wystarczające do przechowywania 32-bitowych wartości całkowitych.
  2. Umieszczono wartość 3 w alokacji pamięci przypisanej do A
  3. Umieszczono wartość 3 w alokacji pamięci przypisanej do B, przypisując jej tę samą wartość, co wartość przechowywana w A.

Wartość każdej zmiennej istnieje dyskretnie w każdej lokalizacji pamięci.

B. Typy referencyjne mogą mieć różne rozmiary. Dlatego nie można ich przechowywać w „stosie” (pamiętaj, że stos jest zbiorem alokacji pamięci o stałym rozmiarze?). Są one przechowywane w „Zarządzanym stosie”. Wskaźniki (lub „odwołania”) do każdego elementu na zarządzanej stercie są utrzymywane na stosie (podobnie jak adres). Twój kod używa tych wskaźników w stosie, aby uzyskać dostęp do obiektów przechowywanych w zarządzanej stercie. Więc kiedy twój kod używa zmiennej referencyjnej, w rzeczywistości używa wskaźnika (lub „adresu” do lokalizacji pamięci w zarządzanej stercie).

Powiedzmy, że utworzyłeś klasę o nazwie clsPerson z ciągiem Property Person.Name

W takim przypadku, gdy składasz takie oświadczenie:

Dim p1 As clsPerson
p1 = New clsPerson
p1.Name = "Jim Morrison"

Dim p2 As Person

p2 = p1

W powyższym przypadku właściwość p1.Name zwróci „Jim Morrison”, jak można się spodziewać. Właściwość p2.Name zwróci RÓWNIEŻ „Jim Morrison”, zgodnie z intuicyjnym oczekiwaniem. Uważam, że zarówno p1, jak i p2 reprezentują różne adresy w stosie. Jednak teraz, gdy przypisałeś p2 wartość p1, zarówno p1, jak i p2 wskazują na SAMEJ LOKALIZACJĘ na zarządzanym stercie.

Teraz rozważ tę sytuację:

Dim p1 As clsPerson
Dim p2 As clsPerson

p1 = New clsPerson
p1.Name = "Jim Morrison"

p2 = p1

p2.Name = "Janis Joplin"

W tej sytuacji utworzyłeś jedną nową instancję klasy osoby na stercie zarządzanym ze wskaźnikiem p1 na stosie, który odwołuje się do obiektu, i ponownie przypisałeś właściwości nazwy instancji obiektu wartość „Jim Morrison”. Następnie utworzyłeś inny wskaźnik p2 w stosie i wskazałeś go na ten sam adres na zarządzanym stercie, jak ten, do którego odwołuje się p1 (kiedy wykonałeś przypisanie p2 = p1).

Nadchodzi zwrot akcji. Kiedy przypiszesz właściwość nazwy p2 wartość "Janis Joplin", zmieniasz właściwość Nazwa dla obiektu REFERENCED zarówno przez p1, jak i p2, tak że jeśli uruchomisz następujący kod:

MsgBox(P1.Name)
'Will return "Janis Joplin"

MsgBox(p2.Name)
'will ALSO return "Janis Joplin"Because both variables (Pointers on the Stack) reference the SAME OBJECT in memory (an Address on the Managed Heap). 

Czy to ma sens?

Ostatni, ubiegły, zeszły. Jeśli to zrobisz:

DIm p1 As New clsPerson
Dim p2 As New clsPerson

p1.Name = "Jim Morrison"
p2.Name = "Janis Joplin"

Masz teraz dwa różne obiekty Person. Jednak w chwili, gdy zrobisz TO ponownie:

p2 = p1

Skierowałeś teraz oba z powrotem do „Jima Morrisona”. (Nie jestem do końca pewien, co się stało z obiektem na stercie, do którego odwołuje się p2.. MYŚLĘ, że teraz wyszedł poza zakres. Jest to jeden z tych obszarów, gdzie mam nadzieję, że ktoś może mnie wyprostować ...). -EDIT: WIERZĘ, że właśnie dlatego przed wykonaniem nowego przypisania należy ustawić p2 = Nothing LUB p2 = New clsPerson.

Jeszcze raz, jeśli teraz zrobisz TO:

p2.Name = "Jimi Hendrix"

MsgBox(p1.Name)
MsgBox(p2.Name)

Oba msgBoxes będą teraz zwracać „Jimi Hendrix”

Może to być trochę zagmatwane i powiem ostatni raz, że niektóre szczegóły mogą być błędne.

Powodzenia i miejmy nadzieję, że inni, którzy wiedzą lepiej ode mnie, pomogą w wyjaśnieniu niektórych z tego. . .

XIVSolutions
źródło
Nie wiem, dlaczego nie otrzymałeś żadnych pozytywnych głosów. Dobra odpowiedź pomogła mi zrozumieć dzięki jasnym, prostym przykładom.
Harry
Jeśli chodzi o koncepcje typu wartości i typu referencyjnego, są one jednolite w całej witrynie .net, są one w rzeczywistości zdefiniowane w specyfikacji Common Language Infrastructure (CLI), standardzie Ecma 335 (również w standardzie ISO). To jest standard dla standardowej części .Net. Standard Ecma 334 (również standard ISO) jest językiem C # i wyraźnie stwierdza, że ​​implementacje C # muszą polegać na interfejsie wiersza polecenia lub obsługiwać alternatywny sposób uzyskiwania minimalnych funkcji interfejsu wiersza polecenia wymaganych przez ten standard C # . VB.Net nie jest jednak standardem, jest własnością firmy Microsoft.
user34660
5

wartość typ danych i typ danych odniesienia

1) wartość (zawiera dane bezpośrednio), ale odniesienie (odnosi się do danych)

2) w wartości (każda zmienna ma swoją własną kopię), ale
w odniesieniu (więcej niż zmienna może odnosić się do niektórych obiektów)

3) w wartości (zmienna operacyjna nie może wpływać na inną zmienną), ale w referencji (zmienna może wpływać na inne)

4) typy wartości to (int, bool, float), ale typ referencyjny to (tablica, obiekty klas, string)

Mohamed Elmasry
źródło
2

Typ wartości:

  • Stały rozmiar pamięci.

  • Przechowywane w pamięci stosu.

  • Zawiera aktualną wartość.

    Dawny. int, char, bool itp ...

Typ odniesienia:

  • Pamięć nieustalona.

  • Przechowywane w pamięci sterty.

  • Przechowuje adres pamięci aktualnej wartości.

    Dawny. ciąg, tablica, klasa itp ...

Dhinagaran P
źródło
1

„Zmienne oparte na typach wartości bezpośrednio zawierają wartości. Przypisanie jednej zmiennej typu wartości do innej powoduje skopiowanie zawartej wartości. Różni się to od przypisania zmiennych typu referencyjnego, które kopiuje odniesienie do obiektu, ale nie sam obiekt”. z biblioteki Microsoft.

Pełniejszą odpowiedź można znaleźć tutaj i tutaj .

Lucas S.
źródło
1
Nie podoba mi się to wyjaśnienie, ponieważ wygląda na to, że przypisanie działa inaczej dla typów referencyjnych i typów wartości. Tak nie jest. W obu przypadkach powoduje to, że wartość zmiennej „docelowej” jest równa wyrażeniu - wartość jest kopiowana. Różnica jest w tym, co to jest wartość nie jest - dla typów referencyjnych, wartość, która zostanie skopiowana jest odwołaniem. To wciąż jest wartość zmiennej.
Jon Skeet,
Zgadzam się z Tobą i już wiedziałem, że mogłoby być inaczej, o czym przeczytasz w tym artykule . Ale po prostu powtarzam przewodnik Microsoftu na ten temat, a także to, jak zwykle czytasz w książkach. Proszę, nie obwiniaj mnie! :)
Lucas S.
Jasne ... jest wiele fragmentów dokumentacji MSDN, w których można znaleźć błąd :)
Jon Skeet,
1

Czasami wyjaśnienia nie pomogą, zwłaszcza początkującym. Możesz wyobrazić sobie typ wartości jako plik danych, a typ odniesienia jako skrót do pliku.

Więc jeśli kopiujesz zmienną referencyjną, kopiujesz tylko link / wskaźnik do rzeczywistych danych gdzieś w pamięci. Jeśli skopiujesz typ wartości, naprawdę klonujesz dane w pamięci.

Chmura Nime
źródło
0

Jest to prawdopodobnie błędne w ezoteryczny sposób, ale, aby to uprościć:

Typy wartości to wartości, które są przekazywane normalnie „według wartości” (a więc ich kopiowanie). Typy referencyjne są przekazywane „przez referencję” (dając w ten sposób wskaźnik do oryginalnej wartości). Standard .NET ECMA nie gwarantuje, gdzie te „rzeczy” są zapisywane. Możesz zbudować implementację .NET bez stosu lub bez stosu (druga byłaby bardzo złożona, ale prawdopodobnie można by było, używając włókien i wielu stosów)

Struktury są typami wartości (int, bool ... są strukturami lub przynajmniej są symulowane jako ...), klasy są typami referencyjnymi.

Typy wartości pochodzą od System.ValueType. Typ odwołania pochodzi od System.Object.

Teraz ... Na końcu masz typ wartości, „obiekty, do których istnieją odniesienia” i referencje (w C ++ byłyby one nazywane wskaźnikami do obiektów. W .NET są nieprzezroczyste. Nie wiemy, czym one są. Z naszego punktu widzenia one są „uchwytami” obiektu). Te ostatnie są podobne do typów wartości (są przekazywane przez kopię). Tak więc obiekt składa się z obiektu (typu referencyjnego) i zera lub większej liczby odniesień do niego (które są podobne do typów wartości). Gdy nie ma żadnych odniesień, GC prawdopodobnie je zbierze.

Ogólnie (w "domyślnej" implementacji .NET), typ wartości może trafić na stos (jeśli są polami lokalnymi) lub na stercie (jeśli są polami klasy, jeśli są zmiennymi w funkcji iteratora, jeśli są zmiennymi, do których odwołuje się zamknięcie, jeśli są zmiennymi w funkcji asynchronicznej (przy użyciu nowszego Async CTP) ...). Wartość, do której odwołuje się odwołanie, może trafić tylko do stosu. Odniesienia używają tych samych reguł, co typy wartości.

W przypadkach typu wartości, które trafiają na stertę, ponieważ znajdują się w funkcji iteratora, funkcji asynchronicznej lub odwołuje się do nich zamknięcie, jeśli obejrzysz skompilowany plik, zobaczysz, że kompilator utworzył klasę, aby umieścić te zmienne , a klasa jest budowana, gdy wywołujesz funkcję.

Teraz nie wiem, jak pisać długie rzeczy i mam lepsze rzeczy do zrobienia w moim życiu. Jeśli chcesz uzyskać „dokładną”, „akademicką”, „poprawną” wersję, przeczytaj TEN:

http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx

To 15 minut, szukam tego! Jest lepszy niż wersje msdn, ponieważ jest to skondensowany artykuł „gotowy do użycia”.

xanatos
źródło
1
To coś więcej niż ezoteryczny sposób. Powiedziałbym, że jest to zasadniczo błędne - ponieważ wartości typu referencyjnego są nadal przekazywane przez wartość; po prostu wartość jest odniesieniem, a nie obiektem. Zobacz pobox.com/~skeet/csharp/parameters.html . Aha, a zmienne lokalne mogą również znaleźć się na stercie, na przykład jeśli zostaną przechwycone lub stanowią część bloku iteratora.
Jon Skeet,
Bloki iteratorów są konwertowane na klasy, więc „za Tobą” są „polami klasy”. To samo dotyczy zamknięć. Tak ... zapomniałem napisać rozróżnienie między „wskaźnikiem” (odniesieniem) a „spiczastym”
xanatos,
@xanatos: Jasne, są to pola klasy po kompilacji - ale nadal są zmiennymi lokalnymi w kodzie źródłowym. Nie nazwałbym też samych referencji „typami wartości” - myślę, że wiem, skąd pochodzisz, ale nie sądzę, aby mycie wód w ten sposób było dobrym pomysłem.
Jon Skeet,
@jon Tak ... To trzeci typ, ponieważ wskaźniki są „nieprzezroczyste” w .net i nie pochodzą z ValueType. Ale są bardziej podobne do typów wartości niż do odwołań. Możesz je „odnieść” i „odrzucić”. Musiałem zmulić wody, ponieważ „ktoś” musiał czepiać się działania iteratorów.
Xanatos
Patrząc na artykuł, na który teraz wskazuję, stwierdziłem: „Istnieją trzy rodzaje wartości: (1) wystąpienia typów wartości, (2) wystąpienia typów odwołań i (3) odwołania. (Kod w C # nie może manipulować instancje typów referencyjnych bezpośrednio; zawsze robi to za pośrednictwem odwołania. W niebezpiecznym kodzie typy wskaźników są traktowane jak typy wartości w celu określenia wymagań dotyczących przechowywania ich wartości. ) ".
xanatos,
0

Najprostszym sposobem myślenia o typach referencyjnych jest traktowanie ich jako „identyfikatorów obiektów”; jedyne, co można zrobić z identyfikatorem obiektu, to utworzyć jeden, skopiować jeden, zapytać o typ jednego lub zmodyfikować go lub porównać dwa pod kątem równości. Próba zrobienia czegokolwiek innego z identyfikatorem obiektu będzie traktowana jako skrót do wykonania wskazanej akcji z obiektem, do którego odwołuje się ten identyfikator.

Załóżmy, że mam dwie zmienne X i Y typu Car - typ referencyjny. Tak się składa, że ​​Y zawiera „ID obiektu nr 19531”. Jeśli powiem „X = Y”, spowoduje to, że X zatrzyma „ID obiektu nr 19531”. Zwróć uwagę, że ani X, ani Y nie posiadają samochodu. Samochód, znany również jako „identyfikator obiektu nr 19531”, jest przechowywany w innym miejscu. Kiedy skopiowałem Y do X, wszystko, co zrobiłem, to skopiowanie numeru identyfikacyjnego. Teraz przypuśćmy, że X.Color = Colors.Blue. Taka instrukcja będzie traktowana jako instrukcja, aby znaleźć „obiekt o numerze ID 19531” i pomalować go na niebiesko. Zauważ, że chociaż X i Y odnoszą się teraz do niebieskiego samochodu, a nie żółtego, to stwierdzenie nie wpływa w rzeczywistości na X ani Y, ponieważ oba nadal odnoszą się do „ID obiektu nr 19531”, który nadal jest tym samym samochodem. zawsze był.

supercat
źródło
0

Typy zmiennych i wartość referencyjna są łatwe do zastosowania i dobrze zastosowane w modelu domeny, ułatwiają proces tworzenia.

Aby usunąć jakikolwiek mit dotyczący ilości „typu wartości”, skomentuję, jak jest to obsługiwane na platformie. NET, w szczególności w C # (CSharp), gdy wywoływana jest APIS i wysyłaj parametry według wartości, przez odwołanie, w naszych metodach i funkcjach oraz jak prawidłowo traktować fragmenty tych wartości.

Przeczytaj ten artykuł Wartość i odwołanie typu zmiennej w języku C #

Marcelo Cavalini
źródło
To jest strona z pytaniami i odpowiedziami tylko w języku angielskim, niestety = \. Jednak dziękuję za próbę odpowiedzi. Proszę utwórz pełne odpowiedzi, z linkami tylko jako pomoc (ale nie jako pełna, długotrwała odpowiedź). Zobacz, jak odpowiedzieć .
Jesse
0

Załóżmy, że vjest to wyrażenie / zmienna typu wartości i wyrażenie / zmienna rtypu referencyjnego

    x = v  
    update(v)  //x will not change value. x stores the old value of v

    x = r 
    update(r)  //x now refers to the updated r. x only stored a link to r, 
               //and r can change but the link to it doesn't .

Zatem zmienna typu wartości przechowuje rzeczywistą wartość (5 lub „h”). Zmienna typu referencyjnego przechowuje tylko odsyłacz do pola metaforycznego, w którym znajduje się wartość.

Anas Elghafari
źródło
0

Przed wyjaśnieniem różnych typów danych dostępnych w C #, ważne jest, aby wspomnieć, że C # jest językiem o jednoznacznie określonym typie. Oznacza to, że każda zmienna, stała, parametr wejściowy, typ zwracany i ogólnie każde wyrażenie, którego wynikiem jest wartość, ma typ.

Każdy typ zawiera informacje, które zostaną osadzone przez kompilator w pliku wykonywalnym jako metadane, które będą używane przez środowisko uruchomieniowe języka wspólnego (CLR), aby zagwarantować bezpieczeństwo typów podczas przydzielania i odzyskiwania pamięci.

Jeśli chcesz wiedzieć, ile pamięci przydziela określony typ, możesz użyć operatora sizeof w następujący sposób:

static void Main()
{
    var size = sizeof(int);
    Console.WriteLine($"int size:{size}");
    size = sizeof(bool);
    Console.WriteLine($"bool size:{size}");
    size = sizeof(double);
    Console.WriteLine($"double size:{size}");
    size = sizeof(char);
    Console.WriteLine($"char size:{size}");
}

Dane wyjściowe pokażą liczbę bajtów przydzielonych przez każdą zmienną.

int size:4
bool size:1
double size:8
char size:2

Informacje związane z każdym typem to:

  • Wymagana przestrzeń magazynowa.
  • Wartości maksymalne i minimalne. Na przykład typ Int32 akceptuje wartości od 2147483648 do 2147483647.
  • Typ podstawowy, z którego dziedziczy.
  • Lokalizacja, w której pamięć dla zmiennych zostanie przydzielona w czasie wykonywania.
  • Rodzaje operacji, które są dozwolone.
  • Elementy członkowskie (metody, pola, zdarzenia itp.) Zawarte w typie. Na przykład, jeśli sprawdzimy definicję typu int, znajdziemy następującą strukturę i składowe:

    namespace System
    {
        [ComVisible(true)]
        public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<Int32>, IEquatable<Int32>
        {      
            public const Int32 MaxValue = 2147483647;     
            public const Int32 MinValue = -2147483648;
            public static Int32 Parse(string s, NumberStyles style, IFormatProvider provider);    
            ... 
        }  
    }

Zarządzanie pamięcią Kiedy w systemie operacyjnym działa wiele procesów, a ilość pamięci RAM nie jest wystarczająca, aby wszystko pomieścić, system operacyjny mapuje części dysku twardego z pamięcią RAM i rozpoczyna przechowywanie danych na dysku twardym. System operacyjny użyje niż określone tabele, w których adresy wirtualne są mapowane na odpowiadające im adresy fizyczne w celu wykonania żądania. Ta możliwość zarządzania pamięcią nazywana jest pamięcią wirtualną.

W każdym procesie dostępna pamięć wirtualna jest zorganizowana w następujących 6 sekcjach, ale ze względu na znaczenie tego tematu skupimy się tylko na stosie i stercie.

Stos Stos jest strukturą danych LIFO (ostatnie weszło, pierwsze wyszło), z wielkością zależną od systemu operacyjnego (domyślnie dla maszyn ARM, x86 i x64 system Windows rezerwuje 1 MB, podczas gdy Linux rezerwuje od 2 MB do 8 MB w zależności od wersja).

Ta sekcja pamięci jest automatycznie zarządzana przez procesor. Za każdym razem, gdy funkcja deklaruje nową zmienną, kompilator przydziela na stosie nowy blok pamięci o wielkości odpowiadającej jej rozmiarowi, a po zakończeniu funkcji blok pamięci dla zmiennej jest zwalniany.

Sterta Ten region pamięci nie jest zarządzany automatycznie przez procesor, a jego rozmiar jest większy niż stos. Po wywołaniu słowa kluczowego new kompilator zaczyna szukać pierwszego wolnego bloku pamięci, który pasuje do rozmiaru żądania. a kiedy go znajdzie, jest oznaczany jako zarezerwowany za pomocą wbudowanej funkcji malloc () w języku C i zwraca wskaźnik do tej lokalizacji. Możliwe jest również zwolnienie bloku pamięci za pomocą wbudowanej funkcji free () w języku C. Mechanizm ten powoduje fragmentację pamięci i musi używać wskaźników, aby uzyskać dostęp do odpowiedniego bloku pamięci, jest wolniejszy niż stos, aby wykonać operacje odczytu / zapisu.

Typy niestandardowe i wbudowane Podczas gdy C # zapewnia standardowy zestaw wbudowanych typów reprezentujących liczby całkowite, wartości logiczne, znaki tekstowe i tak dalej, możesz użyć konstrukcji, takich jak struct, class, interface i enum, aby utworzyć własne typy.

Przykład niestandardowego typu używającego konstrukcji struct:

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

Typy wartości i odwołań Możemy podzielić typ C # na następujące kategorie:

  • Typy wartości
  • Typy referencyjne

Typy wartości Typy wartości pochodzą z klasy System.ValueType, a zmienne tego typu zawierają swoje wartości w ramach alokacji pamięci na stosie. Dwie kategorie typów wartości to struct i enum.

Poniższy przykład przedstawia element członkowski typu boolean. Jak widać, nie ma wyraźnego odwołania do klasy System.ValueType, dzieje się tak, ponieważ ta klasa jest dziedziczona przez strukturę.

namespace System
{
    [ComVisible(true)]
    public struct Boolean : IComparable, IConvertible, IComparable<Boolean>, IEquatable<Boolean>
    {
        public static readonly string TrueString;
        public static readonly string FalseString;
        public static Boolean Parse(string value);
        ...
    }
}

Typy odwołań Z drugiej strony, typy odwołań nie zawierają rzeczywistych danych przechowywanych w zmiennej, ale adres pamięci sterty, w której przechowywana jest wartość. Kategorie typów odwołań to klasy, delegaci, tablice i interfejsy.

W czasie wykonywania, gdy deklarowana jest zmienna typu referencyjnego, zawiera ona wartość null do momentu przypisania do niej obiektu, który został utworzony przy użyciu słów kluczowych new.

Poniższy przykład przedstawia elementy członkowskie List typu ogólnego.

namespace System.Collections.Generic
{
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(Generic.Mscorlib_CollectionDebugView<>))]
    [DefaultMember("Item")]
    public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
    {
        ...
        public T this[int index] { get; set; }
        public int Count { get; }
        public int Capacity { get; set; }
        public void Add(T item);
        public void AddRange(IEnumerable<T> collection);
        ...
    }
}

Jeśli chcesz znaleźć adres pamięci konkretnego obiektu, klasa System.Runtime.InteropServices zapewnia dostęp do zarządzanych obiektów z niezarządzanej pamięci. W poniższym przykładzie użyjemy statycznej metody GCHandle.Alloc () do przydzielenia uchwytu do łańcucha, a następnie metody AddrOfPinnedObject w celu pobrania jego adresu.

string s1 = "Hello World";
GCHandle gch = GCHandle.Alloc(s1, GCHandleType.Pinned);
IntPtr pObj = gch.AddrOfPinnedObject();
Console.WriteLine($"Memory address:{pObj.ToString()}");

Wynik będzie

Memory address:39723832

Referencje Oficjalna dokumentacja: https://docs.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=vs-2019

Ivan Porta
źródło
-1

Istnieje wiele drobnych szczegółów różnic między typami wartości i typami referencyjnymi, które są wyraźnie określone w normie, a niektóre z nich nie są łatwe do zrozumienia, szczególnie dla początkujących.

Zobacz standard ECMA 33, Common Language Infrastructure (CLI) . Interfejs CLI jest również znormalizowany przez ISO. Podałbym odniesienie, ale w przypadku ECMA musimy pobrać plik PDF, a ten link zależy od numeru wersji. Normy ISO kosztują.

Jedna różnica polega na tym, że typy wartości można umieszczać w ramkach, ale zazwyczaj nie można ich umieszczać w typach odwołań. Są wyjątki, ale są one dość techniczne.

Typy wartości nie mogą mieć konstruktorów instancji bez parametrów ani finalizatorów i nie mogą odwoływać się do siebie. Odnoszenie się do siebie oznacza na przykład, że jeśli istnieje typ wartości Node, wówczas członkiem Node nie może być węzłem . Myślę, że w specyfikacjach są inne wymagania / ograniczenia, ale jeśli tak, to nie są one zebrane w jednym miejscu.

user34660
źródło