Wiem, że „string” w C # to typ referencyjny. To jest w MSDN. Jednak ten kod nie działa tak, jak powinien:
class Test
{
public static void Main()
{
string test = "before passing";
Console.WriteLine(test);
TestI(test);
Console.WriteLine(test);
}
public static void TestI(string test)
{
test = "after passing";
}
}
Wyjście powinno być „przed przekazaniem” „po przekazaniu”, ponieważ przekazuję ciąg jako parametr i jest to typ referencyjny, druga instrukcja wyjściowa powinna rozpoznawać, że tekst zmienił się w metodzie TestI. Jednak otrzymuję „przed zdaniem” „przed zdaniem”, co sprawia wrażenie, że jest przekazywana przez wartość, a nie przez odniesienie. Rozumiem, że ciągi znaków są niezmienne, ale nie rozumiem, jak to wyjaśniałoby, co się tutaj dzieje. czego mi brakuje? Dzięki.
Odpowiedzi:
Odniesienie do ciągu jest przekazywane przez wartość. Istnieje duża różnica między przekazywaniem odwołania przez wartość a przekazywaniem obiektu przez odwołanie. Szkoda, że w obu przypadkach użyto słowa „odniesienie”.
Jeśli zrobić przekazać odwołanie ciąg przez odniesienie, to będzie działać zgodnie z oczekiwaniami:
Teraz musisz rozróżnić pomiędzy wprowadzeniem zmian w obiekcie, do którego odwołuje się odniesienie, a dokonaniem zmiany w zmiennej (takiej jak parametr), aby umożliwić odwołanie się do innego obiektu. Nie możemy wprowadzać zmian w ciągu, ponieważ ciągi są niezmienne, ale
StringBuilder
zamiast tego możemy to zademonstrować :Więcej szczegółów znajdziesz w moim artykule na temat przekazywania parametrów .
źródło
referenced
jako odpowiedźJeśli musimy odpowiedzieć na pytanie: String jest typem referencyjnym i zachowuje się jak referencja. Przekazujemy parametr, który zawiera odniesienie, a nie rzeczywisty ciąg. Problem tkwi w funkcji:
Parametr
test
zawiera odniesienie do ciągu, ale jest kopią. Mamy dwie zmienne wskazujące na łańcuch. A ponieważ każda operacja na łańcuchach w rzeczywistości tworzy nowy obiekt, tworzymy lokalną kopię tak, aby wskazywała na nowy łańcuch. Ale oryginalnatest
zmienna nie ulega zmianie.Proponowane rozwiązania, które należy umieścić
ref
w deklaracji funkcji i przy wywołaniu działają, ponieważ nie przekażemy wartościtest
zmiennej, ale przekażemy tylko odniesienie do niej. Zatem wszelkie zmiany wewnątrz funkcji będą odzwierciedlać oryginalną zmienną.Chcę powtórzyć na końcu: String jest typem referencyjnym, ale ponieważ jest niezmienny, linia
test = "after passing";
faktycznie tworzy nowy obiekt, a nasza kopia zmiennejtest
jest zmieniana tak, aby wskazywała na nowy ciąg.źródło
Jak powiedzieli inni,
String
typ w .NET jest niezmienny, a jego odwołanie jest przekazywane przez wartość.W oryginalnym kodzie, gdy tylko ta linia zostanie wykonana:
potem
test
już nie odnosząc się do oryginalnego obiektu. Utworzyliśmy nowyString
obiekt i przypisaliśmy gotest
do odwoływania się do tego obiektu na zarządzanym stercie.Wydaje mi się, że wiele osób się tutaj potyka, ponieważ nie ma widocznego formalnego konstruktora, który mógłby im o tym przypomnieć. W tym przypadku dzieje się to za kulisami od czasu
String
typ ma wsparcie językowe w sposobie jego budowy.Dlatego właśnie zmiana na
test
nie jest widoczna poza zakresemTestI(string)
metody - przekazaliśmy odwołanie według wartości i teraz ta wartość się zmieniła! Ale jeśliString
referencja została przekazana przez referencję, to kiedy referencja ulegnie zmianie, zobaczymy ją poza zakresemTestI(string)
metody.Albo ref lub out hasła są potrzebne w tym przypadku. Wydaje mi się, że
out
słowo kluczowe może być nieco lepiej dopasowane do tej konkretnej sytuacji.źródło
out
musi być przypisane w ramach metody, aby kod mógł zostać skompilowany.ref
nie ma takiego wymagania. Równieżout
parametry są inicjalizowane poza metodą - kod w tej odpowiedzi jest kontrprzykładem.out
parametry można inicjalizować poza metodą, ale nie trzeba. W tym przypadku chcemy zainicjowaćout
parametr, aby zademonstrować kwestię naturystring
typu w .NET.Właściwie byłoby tak samo dla każdego obiektu w tej kwestii, tj. Bycie typem referencyjnym i przekazywanie przez referencję to dwie różne rzeczy w języku C #.
To zadziała, ale ma to zastosowanie niezależnie od typu:
Również o tym, że string jest typem referencyjnym, jest również specjalny. Został zaprojektowany tak, aby był niezmienny, więc wszystkie jego metody nie modyfikują instancji (zwracają nową). Ma też kilka dodatkowych elementów do wydajności.
źródło
Oto dobry sposób na zastanowienie się nad różnicą między typami wartości, przekazywaniem przez wartość, typami odwołań i przekazywaniem przez odwołanie:
Zmienna to kontener.
Zmienna typu wartości zawiera instancję. Zmienna typu referencyjnego zawiera wskaźnik do instancji przechowywanej w innym miejscu.
Modyfikacja zmiennej typu wartości powoduje mutację wystąpienia, które zawiera. Modyfikacja zmiennej typu odwołania powoduje mutację wystąpienia, na które wskazuje.
Oddzielne zmienne typu referencyjnego mogą wskazywać na to samo wystąpienie. Dlatego ta sama instancja może zostać zmutowana za pomocą dowolnej zmiennej, która na nią wskazuje.
Argument przekazany przez wartość to nowy kontener z nową kopią zawartości. Argument przekazany przez odwołanie to oryginalny kontener z oryginalną zawartością.
Gdy argument typu wartości jest przekazywany przez wartość: ponowne przypisanie zawartości argumentu nie ma skutku poza zakresem, ponieważ kontener jest unikalny. Modyfikacja argumentu nie ma skutku poza zakresem, ponieważ instancja jest niezależną kopią.
Gdy argument typu odwołania jest przekazywany według wartości: ponowne przypisanie zawartości argumentu nie ma wpływu poza zakresem, ponieważ kontener jest unikalny. Modyfikacja zawartości argumentu wpływa na zakres zewnętrzny, ponieważ skopiowany wskaźnik wskazuje na współdzielone wystąpienie.
Gdy jakikolwiek argument jest przekazywany przez odwołanie: ponowne przypisanie zawartości argumentu ma wpływ na zakres zewnętrzny, ponieważ kontener jest współużytkowany. Modyfikacja treści argumentu wpływa na zakres zewnętrzny, ponieważ zawartość jest udostępniana.
Podsumowując:
Zmienna łańcuchowa jest zmienną typu referencyjnego. Dlatego zawiera wskaźnik do instancji przechowywanej w innym miejscu. Po przekazaniu przez wartość jego wskaźnik jest kopiowany, więc modyfikacja argumentu ciągu powinna wpływać na współdzielone wystąpienie. Jednak wystąpienie ciągu nie ma modyfikowalnych właściwości, więc argument ciągu nie może zostać zmodyfikowany. Po przekazaniu przez odwołanie kontener wskaźnika jest współużytkowany, więc ponowne przypisanie nadal będzie miało wpływ na zakres zewnętrzny.
źródło
„ Obraz jest wart tysiąca słów ”.
Mam tutaj prosty przykład, jest podobny do twojego przypadku.
To jest to, co się stało:
s1
is2
zmienne odnoszą się do tego samego"abc"
obiektu łańcuchowego."abc"
obiekt ciągu nie modyfikuje się (do"def"
), ale"def"
zamiast tego tworzony jest nowy obiekt ciągu, a następnies1
odniesienia do niego.s2
nadal odwołuje się do"abc"
obiektu string, więc to jest wynik.źródło
Powyższe odpowiedzi są pomocne, chciałbym tylko dodać przykład, który moim zdaniem jasno pokazuje, co się dzieje, gdy przekazujemy parametr bez słowa kluczowego ref, nawet jeśli ten parametr jest typem referencyjnym:
źródło
Dla ciekawskich i dla zakończenia rozmowy: Tak, String jest typem referencyjnym :
Pamiętaj jednak, że ta zmiana działa tylko w niebezpiecznym bloku! ponieważ ciągi są niezmienne (z MSDN):
I pamiętaj, że:
źródło
Uważam, że Twój kod jest analogiczny do poniższego i nie powinieneś oczekiwać, że wartość zmieni się z tego samego powodu, dla którego nie byłoby to tutaj:
źródło
Próbować:
źródło