Tworzę funkcję, w której muszę przekazać obiekt, aby mogła zostać zmodyfikowana przez funkcję. Jaka jest różnica pomiędzy:
public void myFunction(ref MyClass someClass)
i
public void myFunction(out MyClass someClass)
Z których powinienem korzystać i dlaczego?
MyClass
że będzie toclass
typ, tj. Typ referencyjny. W takim przypadku przekazany obiekt można zmodyfikować zamyFunction
pomocą słowa kluczowego parzyste bezref
/ /out
.myFunction
otrzyma nowe odniesienie, które wskazuje na ten sam obiekt, i może modyfikować ten sam obiekt, ile chce. Różnicaref
spowodowałaby, że słowo kluczowemyFunction
otrzymałoby to samo odwołanie do tego samego obiektu. Byłoby to ważne tylko w przypadkumyFunction
zmiany odniesienia do punktu na inny obiekt.Odpowiedzi:
ref
informuje kompilator, że obiekt został zainicjowany przed wejściem do funkcji, aout
informuje kompilator, że obiekt zostanie zainicjowany wewnątrz funkcji.Chociaż
ref
jest dwukierunkowy,out
jest wyłączny.źródło
W
ref
modyfikujące sposób, że:W
out
modyfikujące sposób, że:źródło
out
, czy można go w ogóle odczytać w ramach metody, zanim zostanie ona ustawiona przez tę metodę, jeśli została zainicjowana przed wywołaniem metody? Mam na myśli, czy wywoływana metoda może odczytać to, co przekazała jej metoda argumentu?Powiedzmy, że Dom pojawia się w kabinie Petera na temat notatki o raportach TPS.
Gdyby Dom był argumentem referencyjnym, miałby wydrukowaną kopię notatki.
Gdyby Dom był sprzeczką, kazałby Peterowi wydrukować nową kopię notatki, aby zabrał go ze sobą.
źródło
Spróbuję swoich sił w wyjaśnieniu:
Myślę, że rozumiemy, w jaki sposób typy wartości działają prawidłowo? Typy wartości to (int, long, struct itp.). Kiedy wyślesz je do funkcji bez polecenia ref, KOPIUJE dane . Cokolwiek zrobisz z tymi danymi w funkcji, wpływa tylko na kopię, a nie na oryginał. Polecenie ref wysyła dane RZECZYWISTE, a wszelkie zmiany wpłyną na dane poza funkcją.
Przejdź do mylącej części, typy referencji:
Utwórzmy typ referencyjny:
Kiedy odkrywasz jakiś obiekt , tworzone są dwie części:
Teraz, gdy wysyłasz jakiś obiekt do metody bez odwołania , KOPIUJ wskaźnik odniesienia , a NIE dane. Więc teraz masz to:
Dwa odniesienia do tego samego obiektu. Jeśli zmodyfikujesz właściwość na jakimś obiekcie za pomocą referencji2, wpłynie to na te same dane, na które wskazuje referencja1.
Jeśli wyzerujesz referencję2 lub wskażesz nowe dane, nie wpłynie to na referencję1 ani na referencję danych1.
Co się stanie, gdy wyślesz jakiś obiekt przez odwołanie do metody? Rzeczywiste odniesienie do someObject zostanie wysłany do metody. Masz teraz tylko jedno odniesienie do danych:
Ale co to znaczy? Działa dokładnie tak samo, jak wysyłanie jakiegoś obiektu nie przez referencję, z wyjątkiem dwóch głównych rzeczy:
1) Kiedy zerujesz referencję w metodzie, zeruje ona referencję spoza metody.
2) Możesz teraz wskazać odwołanie do zupełnie innej lokalizacji danych, a odwołanie poza funkcją będzie teraz wskazywać nową lokalizację danych.
źródło
ref
iout
parametry.out
słowa kluczowego?ref jest wchodzące i wychodzące .
Powinieneś używać
out
preferencji wszędzie tam, gdzie to wystarcza dla twoich wymagań.źródło
na zewnątrz:
W języku C # metoda może zwrócić tylko jedną wartość. Jeśli chcesz zwrócić więcej niż jedną wartość, możesz użyć słowa kluczowego out. Modyfikator wyjściowy powraca jako return-by-reference. Najprostsza odpowiedź jest taka, że słowo kluczowe „out” jest używane do uzyskania wartości z metody.
ref:
W języku C #, gdy przekazujesz typ wartości, taki jak int, float, double itp. Jako argument do parametru metody, jest on przekazywany przez wartość. Dlatego zmiana wartości parametru nie wpływa na argument w wywołaniu metody. Ale jeśli zaznaczysz parametr słowem kluczowym „ref”, będzie on odzwierciedlał rzeczywistą zmienną.
źródło
Przykład rozszerzenia psa, kota. Druga metoda z ref zmienia obiekt, do którego odwołuje się wywołujący. Stąd „Kot” !!!
źródło
Ponieważ podajesz typ referencyjny (klasę), nie ma potrzeby używania,
ref
ponieważ domyślnie przekazywane jest tylko odwołanie do rzeczywistego obiektu, a zatem zawsze zmieniasz obiekt za referencją.Przykład:
Tak długo, jak zaliczasz klasę, nie musisz jej używać,
ref
jeśli chcesz zmienić obiekt w swojej metodzie.źródło
someObject = null
abyBar
zakończyć wykonywanie. Twój kod będzie działał poprawnie, ponieważBar
odwołanie tylko do instancji zostało anulowane. Teraz zmieniaBar
sięBar(ref MyClass someObject)
i wykonać ponownie - dostanieszNullReferenceException
boFoo
„s odniesienie do instancji został nulled też.ref
iout
zachowują się podobnie, z wyjątkiem następujących różnic.ref
Zmienna musi zostać zainicjowana przed użyciem.out
Zmienna może być używana bez przypisaniaout
parametr musi być traktowany jako nieprzypisana wartość przez funkcję, która go używa. Możemy więc użyć zainicjowanegoout
parametru w kodzie wywołującym, ale wartość zostanie utracona podczas wykonywania funkcji.źródło
Dla tych, którzy uczą się na przykładzie (jak ja), oto, co mówi Anthony Kolesov .
Stworzyłem kilka minimalnych przykładów ref, out i innych, aby zilustrować tę kwestię. Nie omawiam najlepszych praktyk, tylko przykłady, aby zrozumieć różnice.
https://gist.github.com/2upmedia/6d98a57b68d849ee7091
źródło
"Piekarz"
To dlatego, że pierwszy zmienia odniesienie do ciągu na „Baker”. Zmiana referencji jest możliwa, ponieważ przekazałeś ją przez słowo kluczowe ref (=> referencja do referencji do ciągu). Drugie wywołanie otrzymuje kopię odwołania do ciągu.
Sznurek na początku wygląda na specjalny. Ale ciąg znaków to tylko klasa referencyjna i jeśli ją zdefiniujesz
następnie s jest odwołaniem do klasy łańcuchowej, która zawiera tekst „Able”! Kolejne przypisanie do tej samej zmiennej przez
nie zmienia oryginalnego ciągu, ale po prostu tworzy nowe wystąpienie i wskażmy na to wystąpienie!
Możesz to wypróbować za pomocą następującego małego kodu:
Czego oczekujesz? To, co otrzymasz, to wciąż „Zdolny”, ponieważ po prostu ustawiasz odwołanie ws do innej instancji, podczas gdy s2 wskazuje na oryginalną instancję.
EDYCJA: łańcuch znaków jest również niezmienny, co oznacza, że po prostu nie ma metody ani właściwości, która modyfikowałaby istniejącą instancję łańcucha (możesz spróbować znaleźć ją w dokumentacji, ale nie będziesz finsował :-)). Wszystkie metody manipulacji ciągami zwracają nową instancję ciągu! (Dlatego często uzyskuje się lepszą wydajność podczas korzystania z klasy StringBuilder)
źródło
ref oznacza, że wartość parametru ref jest już ustawiona, metoda może ją odczytać i zmodyfikować. Użycie słowa kluczowego ref jest tym samym, co powiedzenie, że wywołujący jest odpowiedzialny za zainicjowanie wartości parametru.
out mówi kompilatorowi, że inicjalizacja obiektu jest odpowiedzialnością funkcji, funkcja musi przypisać do parametru out. Nie wolno pozostawiać nieprzypisanego.
źródło
Out: Instrukcja return może być użyta do zwrócenia tylko jednej wartości z funkcji. Jednak używając parametrów wyjściowych, możesz zwrócić dwie wartości z funkcji. Parametry wyjściowe są jak parametry odniesienia, z tym wyjątkiem, że przenoszą dane z metody, a nie do niej.
Poniższy przykład ilustruje to:
ref: Parametr referencyjny jest referencją do lokalizacji pamięci zmiennej. Po przekazaniu parametrów przez odniesienie, w przeciwieństwie do parametrów wartości, nowa lokalizacja pamięci nie jest tworzona dla tych parametrów. Parametry referencyjne reprezentują tę samą lokalizację pamięci, co rzeczywiste parametry dostarczane do metody.
W języku C # deklarujesz parametry referencyjne za pomocą słowa kluczowego ref. Poniższy przykład to pokazuje:
źródło
ref i out działają tak samo, jak przekazywanie referencji i przekazywanie wskaźników jak w C ++.
W przypadku odwołania argument musi zostać zadeklarowany i zainicjowany.
Na zewnątrz argument musi zostać zadeklarowany, ale może zostać zainicjowany lub nie
źródło
out double Half_nbr
.Czas tworzenia:
(1) Tworzymy metodę wywoływania
Main()
(2) tworzy obiekt List (który jest obiektem typu odwołania) i przechowuje go w zmiennej
myList
.Podczas działania:
(3) Środowisko wykonawcze przydziela pamięć na stosie na # 00, wystarczająco szeroką, aby przechowywać adres (# 00 =
myList
, ponieważ nazwy zmiennych są tak naprawdę aliasami dla lokalizacji pamięci)(4) Środowisko wykonawcze tworzy obiekt listy na stercie w lokalizacji pamięci #FF (wszystkie te adresy są na przykład sake)
(5) Środowisko wykonawcze zapisuje następnie adres początkowy #FF obiektu pod numerem 00 (lub słownie, przechowuje odwołanie do obiektu List we wskaźniku
myList
)Powrót do czasu tworzenia:
(6) Następnie przekazujemy obiekt List jako argument
myParamList
do wywoływanej metodymodifyMyList
i przypisujemy mu nowy obiekt ListPodczas działania:
(7) Środowisko wykonawcze uruchamia procedurę wywoływania dla wywoływanej metody i jako jej część sprawdza typ parametrów.
(8) Po znalezieniu typu odniesienia przydziela pamięć na stosie w pozycji # 04 w celu aliasingu zmiennej parametru
myParamList
.(9) Następnie zapisuje w nim również wartość #FF.
(10) Środowisko wykonawcze tworzy obiekt listy na stercie w miejscu pamięci # 004 i zastępuje #FF w # 04 tą wartością (lub dereferencyjnie oryginalny obiekt List i wskazywał nowy obiekt List w tej metodzie)
Adres w # 00 nie jest zmieniany i zachowuje odniesienie do #FF (lub oryginalny
myList
wskaźnik nie jest zakłócany).Ref kluczowe jest dyrektywą kompilator pominięcia generowania kodu wykonawczego dla (8) i (9), co oznacza, że nie będzie żadnego przydziału sterty parametrów sposobu. Użyje oryginalnego wskaźnika # 00 do działania na obiekcie w #FF. Jeśli oryginalny wskaźnik nie zostanie zainicjowany, środowisko wykonawcze przestanie narzekać, że nie można kontynuować, ponieważ zmienna nie została zainicjowana
Się kluczowe jest dyrektywa kompilator, który dość dużo jest taka sama jak ref z niewielkimi zmianami w (9) i (10). Kompilator oczekuje, że argument nie zostanie zainicjowany i będzie kontynuował (8), (4) i (5), aby utworzyć obiekt na stercie i zapisać swój adres początkowy w zmiennej argumentu. Nie zostanie zgłoszony żaden niezainicjowany błąd, a wszelkie poprzednie zapisane odniesienia zostaną utracone.
źródło
Oprócz tego, że możesz ponownie przypisać zmienną innej osoby do innej instancji klasy, zwróć wiele wartości itp., Używając
ref
lubout
powiadamiając kogoś innego, czego potrzebujesz od niego i co zamierzasz zrobić ze zmienną, którą udostępniaTy nie potrzebujesz
ref
lubout
jeśli wszystko, co zamierzamy zrobić, to Modyfikacja rzeczy wewnątrz naMyClass
przykład, która jest przekazywana w argumenciesomeClass
.someClass.Message = "Hello World"
użycieref
,out
czy nicsomeClass = new MyClass()
wewnątrzmyFunction(someClass)
zamienia obiekt widziany tylkosomeClass
w zakresiemyFunction
metody. Metoda wywołująca nadal wie o oryginalnejMyClass
instancji, którą utworzyła i przekazała do twojej metodyPaństwo musi
ref
alboout
jeśli planujesz zamianęsomeClass
na zewnątrz dla całego nowego obiektu i chcesz metodę telefonicznej, aby zobaczyć swoje zmianysomeClass = new MyClass()
środkumyFunction(out someClass)
zmienia obiekt widziany przez metodę, która wywołałamyFunction
Istnieją inni programiści
I chcą wiedzieć, co zrobisz z ich danymi. Wyobraź sobie, że piszesz bibliotekę, z której będą korzystać miliony programistów. Chcesz, aby wiedzieli, co zrobisz z ich zmiennymi, gdy wywołasz twoje metody
Użycie
ref
powoduje wyrażenie „Przekaż zmienną przypisaną do pewnej wartości, gdy wywołujesz moją metodę. Pamiętaj, że mogę ją zmienić na coś zupełnie innego w trakcie mojej metody. Nie oczekuj, że twoja zmienna będzie wskazywała na stary obiekt kiedy skończę"Użycie
out
powoduje wyrażenie „Przekaż zmienną zastępczą do mojej metody. Nie ma znaczenia, czy ma ona wartość, czy nie; kompilator zmusi mnie do przypisania jej do nowej wartości. Absolutnie gwarantuję, że obiekt wskazany przez twoją zmienna przed wywołaniem mojej metody, będzie inna do czasu, kiedy skończęNawiasem mówiąc, w C # 7.2 jest też
in
modyfikatorA to zapobiega zamianie metody przekazanej instancji na inną instancję. Pomyśl o tym jak powiedz tym milionom programistów: „przekaż mi swoje oryginalne zmienne i obiecuję, że nie zamienię twoich starannie spreparowanych danych na coś innego”.
in
ma pewne cechy szczególne, aw niektórych przypadkach, np. gdy konieczna może być niejawna konwersja, aby twój skrót był kompatybilny zin int
kompilatorem, tymczasowo utworzy int, rozszerzy skrót do niego, przekaże go przez odniesienie i zakończy. Może to zrobić, ponieważ zadeklarowałeś, że nie będziesz z tym bałaganu.Microsoft zrobił to za pomocą
.TryParse
metod na typach numerycznych:Oznaczając parametr
out
, gdy aktywnie go tutaj deklarują „na pewno zmienimy twoją skrupulatnie spreparowaną wartość 98234957 na coś innego”Oczywiście muszą to robić, na przykład w przypadku analizowania typów wartości, ponieważ jeśli metoda analizy składni nie mogłaby zamienić typu wartości na coś innego, nie działałaby zbyt dobrze. Ale wyobraź sobie, że w niektórych metodach była fikcja tworzona biblioteka:
Możesz zobaczyć, że to jest
out
, i dzięki temu możesz wiedzieć, że jeśli spędzasz godziny na zgniataniu liczb, tworzymy idealny SomeClass:Cóż, to była strata czasu, poświęcenie wszystkich tych godzin na stworzenie idealnej klasy. Na pewno zostanie odrzucony i zastąpiony przez PoorlyNamedMethod
źródło
Dla tych, którzy szukają zwięzłej odpowiedzi.
źródło
Aby zilustrować wiele doskonałych wyjaśnień, opracowałem następującą aplikację na konsolę:
AppendWorld
: KopiaStringList
nazwanegoLiStri
jest przekazywana. Na początku metody ta kopia odwołuje się do oryginalnej listy i dlatego można jej użyć do zmodyfikowania tej listy. PóźniejLiStri
odwołuje się do innegoList<string>
obiektu w metodzie, który nie wpływa na oryginalną listę.HalloWelt
:LiStriRef
jest aliasem już zainicjowanegoListStringRef
. PrzekazanyList<string>
obiekt służy do zainicjowania nowego, dlategoref
był konieczny.CiaoMondo
:LiStriOut
jest aliasemListStringOut
i musi zostać zainicjowany.Tak więc, jeśli metoda po prostu modyfikuje obiekt, do którego odwołuje się przekazywana zmienna, kompilator nie pozwoli ci na użycie
out
i nie powinieneś go używać,ref
ponieważ nie pomyliłby to kompilatora, ale czytnika kodu. Jeśli metoda sprawi, że przekazany argument będzie odwoływał się do innego obiektu, użyjref
dla już zainicjowanego obiektu iout
dla metod, które muszą zainicjować nowy obiekt dla przekazanego argumentu. Poza tym,ref
iout
zachowują się tak samo.źródło
Są prawie takie same - jedyną różnicą jest to, że zmienna przekazywana jako parametr wyjściowy nie musi być inicjowana, a metoda wykorzystująca parametr ref musi ustawić na coś.
Parametry ref odnoszą się do danych, które mogą być modyfikowane, parametry out dotyczą danych, które są dodatkowym wyjściem dla funkcji (np. Int.TryParse), które już korzystają z wartości zwracanej dla czegoś.
źródło
Poniżej pokazałem przykład z użyciem zarówno Ref, jak i Out . Teraz wszystko zostanie wyjaśnione na temat ref i out.
W poniższym przykładzie, gdy komentuję // myRefObj = new myClass {Name = "ref outside call !!"}; otrzyma błąd z informacją „Użyj nieprzypisanej zmiennej lokalnej„ myRefObj ”” , ale nie ma takiego błędu na zewnątrz .
Gdzie używać Ref : gdy wywołujemy procedurę z parametrem in, a ten sam parametr zostanie użyty do przechowywania danych wyjściowych tego proc.
Gdzie użyć Out: gdy wywołujemy procedurę bez parametru i ten sam parametr zostanie użyty do zwrócenia wartości z tego proc. Zwróć także uwagę na wynik
źródło
możesz sprawdzić ten kod, który opisałby cię zupełnie inaczej, gdy użyjesz „ref” oznacza to, że już zainicjalizowałeś ten int / string
ale kiedy użyjesz „out”, działa to w obu warunkach, gdy inicjujesz ten int / string, ale nie musisz, ale musisz inicjować ten int / string w tej funkcji
źródło
Ref: Słowo kluczowe ref służy do przekazania argumentu jako odwołania. Oznacza to, że gdy wartość tego parametru zostanie zmieniona w metodzie, zostanie odzwierciedlona w metodzie wywołującej. Argument przekazywany za pomocą słowa kluczowego ref musi zostać zainicjowany w metodzie wywołującej, zanim zostanie przekazany do metody wywoływanej.
Out: Słowo kluczowe out służy również do przekazania argumentu takiego jak słowo kluczowe ref, ale argument można przekazać bez przypisywania mu żadnej wartości. Argument przekazany przy użyciu słowa kluczowego out musi zostać zainicjowany w wywołanej metodzie, zanim powróci do metody wywołującej.
Ref i out w przeciążeniu metody
Zarówno ref, jak i out nie mogą być używane jednocześnie w przeciążeniu metody. Jednak ref i out są traktowane inaczej w czasie wykonywania, ale są traktowane tak samo w czasie kompilacji (CLR nie rozróżnia między nimi, podczas gdy tworzył IL dla ref i out).
źródło
Z punktu widzenia metody, która odbiera parametr, różnica między
ref
iout
polega na tym, że C # wymaga, aby metody musiały zapisywać każdyout
parametr przed zwróceniem, i nie mogą robić nic z takim parametrem, poza przekazaniem go jakoout
parametru lub zapisaniem do niego , dopóki nie zostanie przekazany jakoout
parametr do innej metody lub zapisany bezpośrednio. Pamiętaj, że niektóre inne języki nie nakładają takich wymagań; metoda wirtualna lub interfejs zadeklarowana w języku C # zout
parametrem może zostać zastąpiona w innym języku, który nie nakłada żadnych specjalnych ograniczeń na takie parametry.Z punktu widzenia wywołującego C # w wielu okolicznościach zakłada, że wywołanie metody z
out
parametrem spowoduje zapisanie przekazanej zmiennej bez uprzedniego odczytania. To założenie może być nieprawidłowe w przypadku wywoływania metod napisanych w innych językach. Na przykład:Jeśli
myDictionary
identyfikujeIDictionary<TKey,TValue>
implementację napisaną w języku innym niż C #, nawet jeśliMyStruct s = new MyStruct(myDictionary);
wygląda jak przypisanie, może potencjalnie pozostaćs
niezmodyfikowana.Zauważ, że konstruktory napisane w VB.NET, w przeciwieństwie do C #, nie przyjmują żadnych założeń co do tego, czy wywoływane metody będą modyfikować dowolne
out
parametry i bezwarunkowo usuwają wszystkie pola. Dziwne zachowanie wspomniane powyżej nie wystąpi w przypadku kodu napisanego całkowicie w VB lub całkowicie w C #, ale może wystąpić, gdy kod napisany w C # wywoła metodę napisaną w VB.NET.źródło
Jeśli chcesz przekazać parametr jako referencję, powinieneś go zainicjować przed przekazaniem parametru do funkcji, w przeciwnym razie sam kompilator wyświetli błąd, ale w przypadku parametru out nie musisz inicjować parametru obiektu przed przekazaniem go do parametru Możesz zainicjować obiekt w samej metodzie wywołującej.
źródło
Pamiętaj, że parametr referencyjny przekazywany wewnątrz funkcji jest przetwarzany bezpośrednio.
Na przykład,
To napisze Pies, nie Kot. Dlatego powinieneś bezpośrednio pracować na someObject.
źródło
Być może nie jestem w tym dobry, ale z pewnością ciągi znaków (mimo że są technicznie typami referencyjnymi i znajdują się na stosie) są przekazywane według wartości, a nie referencji?
Dlatego potrzebujesz ref, jeśli chcesz, aby zmiany istniały poza zakresem funkcji ich tworzenia, nie przekazujesz referencji inaczej.
O ile mi wiadomo, potrzebujesz tylko ref dla typów struktur / wartości i samego łańcucha, ponieważ łańcuch jest typem odniesienia, który udaje, że jest, ale nie jest typem wartości.
Mogę się jednak całkowicie mylić, jestem nowy.
źródło
Capitalize()
, która zmieniłaby jego zawartość na wielkie litery. Jeśli następnie zastąpić linięa = "testing";
za.Capitalize();
, wówczas wyjście byłoby „Hello”, a nie „Hello”. Jedną z zalet niezmiennych typów jest to, że można przekazywać odniesienia i nie martwić się, że inny kod zmieni wartość.