Kiedy przekazuję a string
do funkcji, czy wskaźnik do zawartości ciągu jest przekazywany, czy też cały ciąg jest przekazywany do funkcji na stosie, tak jak struct
byłby to?
Kiedy przekazuję a string
do funkcji, czy wskaźnik do zawartości ciągu jest przekazywany, czy też cały ciąg jest przekazywany do funkcji na stosie, tak jak struct
byłby to?
Odpowiedzi:
Odwołanie jest przekazywane; jednak nie jest to technicznie przekazywane przez odniesienie. To subtelne, ale bardzo ważne rozróżnienie. Rozważ następujący kod:
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
Aby zrozumieć, co się tutaj dzieje, musisz wiedzieć trzy rzeczy:
strMain
nie są przekazywane przez odwołanie. Jest to typ referencyjny, ale samo odwołanie jest przekazywane przez wartość . Za każdym razem, gdy przekazujesz parametr bez ref
słowa kluczowego (nie licząc out
parametrów), przekazujesz coś według wartości.To musi oznaczać, że ... przekazujesz odwołanie według wartości. Ponieważ jest to typ referencyjny, tylko referencja została skopiowana na stos. Ale co to znaczy?
Zmienne C # są typami odwołań lub typami wartości . Parametry języka C # są przekazywane przez odwołanie lub przez wartość . Terminologia jest tutaj problemem; brzmią jak to samo, ale tak nie jest.
Jeśli przekazujesz parametr DOWOLNEGO typu i nie używasz ref
słowa kluczowego, to przekazujesz go według wartości. Jeśli przekazałeś to według wartości, tak naprawdę przekazałeś kopię. Ale jeśli parametr był typem referencyjnym, to skopiowana rzecz była referencją, a nie tym, na co wskazywała.
Oto pierwsza linia Main
metody:
string strMain = "main";
Stworzyliśmy w tej linii dwie rzeczy: ciąg znaków z wartością main
przechowywaną gdzieś w pamięci oraz zmienną referencyjną o nazwie strMain
wskazującą na nią.
DoSomething(strMain);
Teraz przekazujemy to odniesienie do DoSomething
. Przekazaliśmy to według wartości, więc oznacza to, że utworzyliśmy kopię. Jest to typ referencyjny, co oznacza, że skopiowaliśmy referencję, a nie sam ciąg. Teraz mamy dwa odniesienia, z których każdy wskazuje na tę samą wartość w pamięci.
Oto początek DoSomething
metody:
void DoSomething(string strLocal)
Brak ref
słowa kluczowego, więc strLocal
i strMain
są dwa różne odwołania wskazujące tę samą wartość. Jeśli zmienimy przypisanie strLocal
...
strLocal = "local";
... nie zmieniliśmy przechowywanej wartości; wzięliśmy odwołanie o nazwie strLocal
i wycelowaliśmy go w zupełnie nowy ciąg. Co się stanie, strMain
gdy to zrobimy? Nic. Wciąż wskazuje na stary sznurek.
string strMain = "main"; // Store a string, create a reference to it
DoSomething(strMain); // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main"
Zmieńmy na chwilę scenariusz. Wyobraź sobie, że nie pracujemy z ciągami, ale jakimś zmiennym typem referencyjnym, takim jak utworzona przez Ciebie klasa.
class MutableThing
{
public int ChangeMe { get; set; }
}
Jeśli podążasz za odniesieniem objLocal
do obiektu, na który wskazuje, możesz zmienić jego właściwości:
void DoSomething(MutableThing objLocal)
{
objLocal.ChangeMe = 0;
}
W MutableThing
pamięci wciąż jest tylko jeden i zarówno skopiowane odniesienie, jak i oryginalne odniesienie nadal do niego wskazują. Właściwości MutableThing
samego siebie uległy zmianie :
void Main()
{
var objMain = new MutableThing();
objMain.ChangeMe = 5;
Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain
DoSomething(objMain); // now it's 0 on objLocal
Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain
}
Ach, ale struny są niezmienne! Nie ma ChangeMe
właściwości do ustawienia. Nie możesz tego zrobić strLocal[3] = 'H'
w C # tak, jak w przypadku char
tablicy w stylu C ; musisz zamiast tego skonstruować cały nowy ciąg. Jedynym sposobem zmiany strLocal
jest wskazanie referencji na inny ciąg, a to oznacza, że nic, co robisz, nie ma strLocal
wpływu strMain
. Wartość jest niezmienna, a odwołanie jest kopią.
Aby udowodnić, że istnieje różnica, oto co się dzieje, gdy przekazujesz referencję przez referencję:
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
Tym razem ciąg w Main
naprawdę się zmienia, ponieważ przekazałeś referencję bez kopiowania jej na stos.
Więc nawet jeśli łańcuchy są typami referencyjnymi, przekazywanie ich przez wartość oznacza, że cokolwiek dzieje się w wywoływanym, nie wpłynie na ciąg w wywołującym. Ale ponieważ są to typy referencyjne, nie musisz kopiować całego ciągu do pamięci, gdy chcesz go przekazać.
ref
słowa kluczowego. Aby udowodnić, że przekazywanie przez odniesienie robi różnicę, obejrzyj to demo: rextester.com/WKBG5978
ref
słowo kluczowe ma użyteczność, próbowałem tylko wyjaśnić, dlaczego można pomyśleć o przekazywaniu typu referencyjnego przez wartość w C # wydaje się być "tradycyjnym" (tj. C) pojęciem przekazywania przez referencję (i przekazywania typu referencyjnego przez odwołanie w C # wydaje się bardziej przypominać przekazywanie odwołania do odwołania według wartości).
Foo(string bar)
może być traktowana jako Foo(char* bar)
podczas gdy Foo(ref string bar)
byłaby Foo(char** bar)
( Foo(char*& bar)
lub Foo(string& bar)
w C ++). Jasne, nie tak powinieneś o tym myśleć każdego dnia, ale tak naprawdę pomogło mi to w końcu zrozumieć, co dzieje się pod maską.
Ciągi w języku C # są niezmiennymi obiektami referencyjnymi. Oznacza to, że odniesienia do nich są przekazywane (według wartości), a po utworzeniu ciągu nie można go modyfikować. Metody, które tworzą zmodyfikowane wersje ciągu (podciągi, wersje przycięte itp.) Tworzą zmodyfikowane kopie oryginalnego ciągu.
Ciągi znaków to szczególne przypadki. Każda instancja jest niezmienna. Kiedy zmieniasz wartość ciągu, przydzielasz nowy ciąg w pamięci.
Więc tylko referencja jest przekazywana do twojej funkcji, ale kiedy ciąg jest edytowany, staje się nową instancją i nie modyfikuje starej instancji.
Uri
(klasa) i Guid
(struktura) są również przypadkami specjalnymi. Nie widzę, jak System.String
działa jak „typ wartości” bardziej niż inne niezmienne typy… pochodzenia klasowego lub strukturalnego.
Uri
& Guid
- możesz po prostu przypisać wartość literału ciągu do zmiennej ciągu. Ciąg wydaje się być zmienny, jak int
przypisanie, ale niejawnie tworzy obiekt - bez new
słowa kluczowego.