Jaka jest różnica między słowami kluczowymi „ref” i „out”?


891

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?


69
Ty: Muszę przekazać obiekt, aby można go było modyfikować Wygląda na to, MyClassże będzie to classtyp, tj. Typ referencyjny. W takim przypadku przekazany obiekt można zmodyfikować za myFunctionpomocą słowa kluczowego parzyste bez ref/ / out. myFunctionotrzyma nowe odniesienie, które wskazuje na ten sam obiekt, i może modyfikować ten sam obiekt, ile chce. Różnica refspowodowałaby, że słowo kluczowe myFunctionotrzymałoby to samo odwołanie do tego samego obiektu. Byłoby to ważne tylko w przypadku myFunctionzmiany odniesienia do punktu na inny obiekt.
Jeppe Stig Nielsen

3
Zastanawia mnie ilość mylących odpowiedzi tutaj, gdy @ AnthonyKolesov's jest całkiem idealny.
o0 ”.

Zadeklarowanie metody out jest przydatne, gdy metoda ma zwracać wiele wartości. Jeden argument może być przypisany do null. Umożliwia to metodom opcjonalne zwracanie wartości.
Jewgraf Andriejiewicz Zhivago

Tutaj wyjaśniono w przykładzie To bardziej zrozumiałe :) dotnet-tricks.com/Tutorial/csharp/…
Prageeth godage

2
@ Komentarz JeppeStigNielsen jest technicznie (jedyną) poprawną odpowiedzią na aktualne pytanie PO. Aby przekazać obiekt do metody, aby metoda mogła zmodyfikować obiekt , wystarczy przekazać obiekt (odwołanie do) do metody według wartości. Zmiana obiektu w metodzie za pomocą argumentu obiektu modyfikuje oryginalny obiekt , nawet jeśli metoda zawiera własną oddzielną zmienną (która odwołuje się do tego samego obiektu).
David R Tribble

Odpowiedzi:


1160

refinformuje kompilator, że obiekt został zainicjowany przed wejściem do funkcji, a outinformuje kompilator, że obiekt zostanie zainicjowany wewnątrz funkcji.

Chociaż refjest dwukierunkowy, outjest wyłączny.


270
Kolejną fajną rzeczą charakterystyczną dla out jest to, że funkcja musi przypisać parametr out. Nie wolno pozostawiać nieprzypisanego.
Daniel Earwicker,

7
czy „ref” dotyczy tylko typu wartości? Ponieważ typ odniesienia jest zawsze przekazywany przez ref.
wadliwy

3
Tak. Typy wartości, w tym struktury
Rune Grimstad

17
@faulty: Nie, odniesienie dotyczy nie tylko typów wartości. ref / out są jak wskaźniki w C / C ++, zajmują się lokalizacją pamięci obiektu (pośrednio w C #) zamiast obiektu bezpośredniego.
thr

52
@faulty: Wbrew intuicji, typy referencyjne są zawsze przekazywane przez wartość w C #, chyba że użyjesz specyfikatora ref. Jeśli ustawisz myval = somenewval, efekt znajdzie się tylko w zakresie tej funkcji. Słowo kluczowe ref pozwoli Ci zmienić myval, tak aby wskazywał na somenewval.
JasonTrue 14.04.2010

535

W refmodyfikujące sposób, że:

  1. Wartość jest już ustawiona i
  2. Metoda może ją odczytać i zmodyfikować.

W outmodyfikujące sposób, że:

  1. Wartość nie jest ustawiona i nie można jej odczytać, dopóki nie zostanie ustawiona.
  2. Metoda musi to ustawić przed powrotem.

30
Ta odpowiedź w sposób najbardziej przejrzysty i zwięzły wyjaśnia ograniczenia nałożone przez kompilator podczas używania słowa kluczowego out w przeciwieństwie do słowa kluczowego ref.
Dr Wily's Apprentice

5
Z MSDN: parametr ref musi zostać zainicjowany przed użyciem, podczas gdy parametr out nie musi być jawnie zainicjowany przed przekazaniem, a każda poprzednia wartość jest ignorowana.
Shiva Kumar,

1
Za pomocą 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?
Panzercrisis,

3
Panzercrisis, dla „out”, wywoływana metoda może odczytać, jeśli jest już ustawiona. ale musi to ustawić ponownie.
robert jebakumar2 30.04.17

146

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ą.


54
ref Dom
napisałby

6
@ Deebster, wiesz, że ta metafora nigdy ci nic nie zrobiła, dlaczego tak ją torturujesz? ;)
Michael Blackburn

21
zabawny, ale edukujący, stackoverflow potrzebuje więcej takich postów
Frank Visaggio

2
Na wypadek gdyby ktoś uznał tę odpowiedź za na wpół zabawną, obejrzyj film „Powierzchnia biurowa”.
displayName

a szef Dom i Peters stałby zachowywać się jak Dom (jako nasz argument), zmuszając obydwu do wydrukowania go od nowa, dopóki Peter nie poda Domdowi wydruku
Patrick Artner,

57

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:

List<string> someobject = new List<string>()

Kiedy odkrywasz jakiś obiekt , tworzone są dwie części:

  1. Blok pamięci przechowujący dane dla niektórych obiektów .
  2. Odwołanie (wskaźnik) do tego bloku danych.

Teraz, gdy wysyłasz jakiś obiekt do metody bez odwołania , KOPIUJ wskaźnik odniesienia , a NIE dane. Więc teraz masz to:

(outside method) reference1 => someobject
(inside method)  reference2 => someobject

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.

 (inside method)  reference2.Add("SomeString");
 (outside method) reference1[0] == "SomeString"   //this is true

Jeśli wyzerujesz referencję2 lub wskażesz nowe dane, nie wpłynie to na referencję1 ani na referencję danych1.

(inside method) reference2 = new List<string>();
(outside method) reference1 != null; reference1[0] == "SomeString" //this is true

The references are now pointing like this:
reference2 => new List<string>()
reference1 => someobject

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:

(outside method) reference1 => someobject;
(inside method)  reference1 => someobject;

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.

 (inside method)  reference1 = null;
 (outside method) reference1 == null;  //true

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.

 (inside method)  reference1 = new List<string>();
 (outside method) reference1.Count == 0; //this is true

Masz na myśli przecież (w przypadku referencyjnym) tylko jedno odniesienie do danych, ale dwa aliasy. Dobrze?
Sadiq

3
Głosowano za jasne wyjaśnienie. Ale myślę, że to nie jest odpowiedź na pytanie, jak to nie wyjaśniają różnicę pomiędzy refi outparametry.
Joyce Babu,

1
Niesamowity. czy możesz to wyjaśnić jak dla outsłowa kluczowego?
Asif Mushtaq

28

ref jest wchodzące i wychodzące .

Powinieneś używać outpreferencji wszędzie tam, gdzie to wystarcza dla twoich wymagań.


nie do końca, ponieważ przyjęta odpowiedź odnosi się do kierunkowego i bezużytecznego ignorowania typów wartości, jeśli nie jest przekazywana z powrotem.
kenny

@kenny: Czy możesz wyjaśnić trochę - tj. które słowa byś zmienił, aby zachować ducha odpowiedzi, ale usunąć niedokładność, którą dostrzegasz? Moja odpowiedź nie jest szalonym przypuszczeniem od początkującego, ale pośpiech (zwięzłość, literówki) w twoim komentarzu wydaje się, że tak. Celem jest zapewnienie sposobu myślenia o różnicy przy użyciu jak najmniejszej liczby słów.
Ruben Bartelink

(BTW, znam typy wartości, typy referencji, przekazywanie przez referencję, przekazywanie przez wartość, COM i C ++, jeśli uznasz, że warto odwoływać się do tych pojęć w wyjaśnieniu)
Ruben Bartelink

1
Odwołania do obiektów są przekazywane według wartości (z wyjątkiem przypadków użycia słowa kluczowego „ref” lub „out”). Pomyśl o obiektach jak o numerach identyfikacyjnych. Jeśli zmienna klasy zawiera „Object # 1943” i jeden przekazuje tę zmienną przez wartość do procedury, procedura ta może wprowadzać zmiany do Object # 1943, ale nie może sprawić, że zmienna wskazuje na coś innego niż „Object # 1943”. Jeśli zmienna została przekazana przez odniesienie, procedura może sprawić, że punkt zmiennej będzie zawierał „Obiekt # 5441”.
supercat

1
@ supercat: Podoba mi się twoje wyjaśnienie ref vs. val (i ta anaologia uzupełniająca). Myślę, że Kenny tak naprawdę nie potrzebuje wyjaśnienia mu tego (relatywnie) mylącego, jak jego komentarze. Chciałbym, żebyśmy wszyscy mogli po prostu usunąć te cholerne komentarze, ponieważ wprowadzają wszystkich w błąd. Główną przyczyną wszystkich tych bzdur wydaje się być to, że Kenny źle odczytał moją odpowiedź i jeszcze nie wskazał jednego słowa, które powinno zostać dodane / usunięte / zastąpione. Żadne z nas trzech nie nauczyło się niczego z dyskusji, której jeszcze nie znaliśmy, a druga odpowiedź ma absurdalną liczbę pozytywnych opinii.
Ruben Bartelink

18

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.

  1. Nie musisz inicjować wartości w funkcji wywołującej.
  2. Musisz przypisać wartość do wywoływanej funkcji, w przeciwnym razie kompilator zgłosi błąd.

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ą.

  1. Musisz zainicjować zmienną przed wywołaniem funkcji.
  2. Przypisanie jakiejkolwiek wartości do parametru ref w metodzie nie jest obowiązkowe. Jeśli nie zmienisz wartości, to co trzeba oznaczyć jako „ref”?

„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.” Możemy również użyć „ref” do zwrócenia wartości. Więc możemy użyć zarówno ref, jak i out, jeśli chcemy zwrócić wiele wartości z metody?
Ned

1
W c # 7 możesz zwrócić wiele wartości za pomocą ValueTuples.
Iman Bahrampour,

13

Przykład rozszerzenia psa, kota. Druga metoda z ref zmienia obiekt, do którego odwołuje się wywołujący. Stąd „Kot” !!!

    public static void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog". 
        Bar(ref myObject);
        Console.WriteLine(myObject.Name); // Writes "Cat". 
    }

    public static void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

    public static void Bar(ref MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

8

Ponieważ podajesz typ referencyjny (klasę), nie ma potrzeby używania, refponieważ domyślnie przekazywane jest tylko odwołanie do rzeczywistego obiektu, a zatem zawsze zmieniasz obiekt za referencją.

Przykład:

public void Foo()
{
    MyClass myObject = new MyClass();
    myObject.Name = "Dog";
    Bar(myObject);
    Console.WriteLine(myObject.Name); // Writes "Cat".
}

public void Bar(MyClass someObject)
{
    someObject.Name = "Cat";
}

Tak długo, jak zaliczasz klasę, nie musisz jej używać, refjeśli chcesz zmienić obiekt w swojej metodzie.


5
Działa to tylko wtedy, gdy żaden nowy obiekt nie jest tworzony i zwracany. Po utworzeniu nowego obiektu odniesienie do starego obiektu zostanie utracone.
etsuba,

8
To źle - spróbuj wykonać następujące czynności: dodaj, someObject = nullaby Barzakończyć wykonywanie. Twój kod będzie działał poprawnie, ponieważ Barodwołanie tylko do instancji zostało anulowane. Teraz zmienia Barsię Bar(ref MyClass someObject)i wykonać ponownie - dostaniesz NullReferenceExceptionbo Foo„s odniesienie do instancji został nulled też.
Keith

8

refi outzachowują się podobnie, z wyjątkiem następujących różnic.

  • refZmienna musi zostać zainicjowana przed użyciem. outZmienna może być używana bez przypisania
  • outparametr musi być traktowany jako nieprzypisana wartość przez funkcję, która go używa. Możemy więc użyć zainicjowanego outparametru w kodzie wywołującym, ale wartość zostanie utracona podczas wykonywania funkcji.


6

"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

string s = "Able";

następnie s jest odwołaniem do klasy łańcuchowej, która zawiera tekst „Able”! Kolejne przypisanie do tej samej zmiennej przez

s = "Baker";

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:

string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);

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)


1
Dokładnie. Dlatego nie jest do końca prawdą stwierdzenie „Ponieważ podajesz typ referencyjny (klasę), nie ma potrzeby używania ref”.
Paul Mitchell,

Teoretycznie słusznie jest tak powiedzieć, ponieważ napisał „aby można go było modyfikować”, co nie jest możliwe w przypadku ciągów. Ale ze względu na niezmienne obiekty „ref” i „out” są bardzo przydatne również dla typów referencyjnych! (.Net zawiera wiele niezmiennych klas!)
mmmmmmmm

Tak, masz rację. Nie myślałem o niezmiennych obiektach takich jak łańcuchy, ponieważ większość obiektów jest zmienna.
Albic

1
Cóż, to jest zagadkowa odpowiedź do zobaczenia w LQP, dla pewności; nie ma w tym nic złego, poza tym, że wydaje się, że jest to długa i dokładna odpowiedź na inny komentarz (ponieważ w pierwotnym pytaniu nie ma wzmianki o Able i Baker w żadnej z jego wersji), jakby to było forum. Wydaje mi się, że nie było to jeszcze tak dawno wyjaśnione.
Nathan Tuggy

6

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.


5

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:

using System;

namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void getValue(out int x )
      {
         int temp = 5;
         x = temp;
      }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;

         Console.WriteLine("Before method call, value of a : {0}", a);

         /* calling a function to get the value */
         n.getValue(out a);

         Console.WriteLine("After method call, value of a : {0}", a);
         Console.ReadLine();

      }
   }
}

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:

using System;
namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void swap(ref int x, ref int y)
      {
         int temp;

         temp = x; /* save the value of x */
         x = y;   /* put y into x */
         y = temp; /* put temp into y */
       }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;
         int b = 200;

         Console.WriteLine("Before swap, value of a : {0}", a);
         Console.WriteLine("Before swap, value of b : {0}", b);

         /* calling a function to swap the values */
         n.swap(ref a, ref b);

         Console.WriteLine("After swap, value of a : {0}", a);
         Console.WriteLine("After swap, value of b : {0}", b);

         Console.ReadLine();

      }
   }
}

4

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

        double nbr = 6; // if not initialized we get error
        double dd = doit.square(ref nbr);

        double Half_nbr ; // fine as passed by out, but inside the calling  method you initialize it
        doit.math_routines(nbr, out Half_nbr);

1
Można zadeklarować zmienną inline: out double Half_nbr.
Sebastian Hofmann

4

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.

public sealed class Program 
{
    public static Main() 
    {
        List<int> myList = new List<int>();

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 myParamListdo wywoływanej metody modifyMyListi przypisujemy mu nowy obiekt List

List<int> myList = new List<int>();

List<int> newList = ModifyMyList(myList)

public List<int> ModifyMyList(List<int> myParamList){
     myParamList = new List<int>();
     return myParamList;
}

Podczas 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 myListwskaź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.


3

Oprócz tego, że możesz ponownie przypisać zmienną innej osoby do innej instancji klasy, zwróć wiele wartości itp., Używając reflub outpowiadamiając kogoś innego, czego potrzebujesz od niego i co zamierzasz zrobić ze zmienną, którą udostępnia

  • Ty nie potrzebujesz ref lub outjeśli wszystko, co zamierzamy zrobić, to Modyfikacja rzeczy wewnątrz na MyClassprzykład, która jest przekazywana w argumencie someClass.

    • Metoda wywołująca zobaczy zmiany takie jak someClass.Message = "Hello World"użycie ref, outczy nic
    • Pisanie someClass = new MyClass()wewnątrz myFunction(someClass)zamienia obiekt widziany tylko someClassw zakresie myFunctionmetody. Metoda wywołująca nadal wie o oryginalnej MyClassinstancji, którą utworzyła i przekazała do twojej metody
  • Państwo musi ref albo outjeśli planujesz zamianę someClassna zewnątrz dla całego nowego obiektu i chcesz metodę telefonicznej, aby zobaczyć swoje zmiany

    • Zapis w someClass = new MyClass()środku myFunction(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 refpowoduje 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 outpowoduje 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ż inmodyfikator

A 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”. inma pewne cechy szczególne, aw niektórych przypadkach, np. gdy konieczna może być niejawna konwersja, aby twój skrót był kompatybilny z in intkompilatorem, 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ą .TryParsemetod na typach numerycznych:

int i = 98234957;
bool success = int.TryParse("123", out i);

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:

public void PoorlyNamedMethod(out SomeClass x)

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:

SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);

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


3

Dla tych, którzy szukają zwięzłej odpowiedzi.

Oba refi outsłowa kluczowe są używane do przekazywania reference.


Zmienna refsłowa kluczowego musi mieć wartość lub musi odnosić się do obiektu lub null przed jego przekazaniem.


W przeciwieństwie refdo zmiennej outsłowo kluczowe musi mieć wartość lub musi odnosić się do obiektu lub null po jego przekazaniu, a także nie musi mieć wartości ani odwoływać się do obiektu przed przekazaniem.


2

Aby zilustrować wiele doskonałych wyjaśnień, opracowałem następującą aplikację na konsolę:

using System;
using System.Collections.Generic;

namespace CSharpDemos
{
  class Program
  {
    static void Main(string[] args)
    {
      List<string> StringList = new List<string> { "Hello" };
      List<string> StringListRef = new List<string> { "Hallo" };

      AppendWorld(StringList);
      Console.WriteLine(StringList[0] + StringList[1]);

      HalloWelt(ref StringListRef);
      Console.WriteLine(StringListRef[0] + StringListRef[1]);

      CiaoMondo(out List<string> StringListOut);
      Console.WriteLine(StringListOut[0] + StringListOut[1]);
    }

    static void AppendWorld(List<string> LiStri)
    {
      LiStri.Add(" World!");
      LiStri = new List<string> { "¡Hola", " Mundo!" };
      Console.WriteLine(LiStri[0] + LiStri[1]);
    }

    static void HalloWelt(ref List<string> LiStriRef)
     { LiStriRef = new List<string> { LiStriRef[0], " Welt!" }; }

    static void CiaoMondo(out List<string> LiStriOut)
     { LiStriOut = new List<string> { "Ciao", " Mondo!" }; }
   }
}
/*Output:
¡Hola Mundo!
Hello World!
Hallo Welt!
Ciao Mondo!
*/
  • AppendWorld: Kopia StringListnazwanego LiStrijest 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óźniej LiStriodwołuje się do innego List<string>obiektu w metodzie, który nie wpływa na oryginalną listę.

  • HalloWelt: LiStriRefjest aliasem już zainicjowanego ListStringRef. Przekazany List<string>obiekt służy do zainicjowania nowego, dlatego refbył konieczny.

  • CiaoMondo: LiStriOutjest aliasem ListStringOuti 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 outi nie powinieneś go używać, refponieważ 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 i outdla metod, które muszą zainicjować nowy obiekt dla przekazanego argumentu. Poza tym, refi outzachowują się tak samo.


1

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ś.

int x;    Foo(out x); // OK 
int y;    Foo(ref y); // Error

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ś.


1

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

public partial class refAndOutUse : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        myClass myRefObj;
        myRefObj = new myClass { Name = "ref outside called!!  <br/>" };
        myRefFunction(ref myRefObj);
        Response.Write(myRefObj.Name); //ref inside function

        myClass myOutObj;
        myOutFunction(out myOutObj);
        Response.Write(myOutObj.Name); //out inside function
    }

    void myRefFunction(ref myClass refObj)
    {
        refObj.Name = "ref inside function <br/>";
        Response.Write(refObj.Name); //ref inside function
    }
    void myOutFunction(out myClass outObj)
    {
        outObj = new myClass { Name = "out inside function <br/>" }; 
        Response.Write(outObj.Name); //out inside function
    }
}

public class myClass
{
    public string Name { get; set; }
} 

1
 public static void Main(string[] args)
    {
        //int a=10;
        //change(ref a);
        //Console.WriteLine(a);
        // Console.Read();

        int b;
        change2(out b);
        Console.WriteLine(b);
        Console.Read();
    }
    // static void change(ref int a)
    //{
    //    a = 20;
    //}

     static void change2(out int b)
     {
         b = 20;
     }

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


1

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.

public class Example
{
 public static void Main() 
 {
 int val1 = 0; //must be initialized 
 int val2; //optional

 Example1(ref val1);
 Console.WriteLine(val1); 

 Example2(out val2);
 Console.WriteLine(val2); 
 }

 static void Example1(ref int value) 
 {
 value = 1;
 }
 static void Example2(out int value) 
 {
 value = 2; 
 }
}

/* Output     1     2     

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).


0

Z punktu widzenia metody, która odbiera parametr, różnica między refi outpolega na tym, że C # wymaga, aby metody musiały zapisywać każdy outparametr przed zwróceniem, i nie mogą robić nic z takim parametrem, poza przekazaniem go jako outparametru lub zapisaniem do niego , dopóki nie zostanie przekazany jako outparametr 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 # z outparametrem 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 outparametrem 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:

struct MyStruct
{
   ...
   myStruct(IDictionary<int, MyStruct> d)
   {
     d.TryGetValue(23, out this);
   }
}

Jeśli myDictionaryidentyfikuje IDictionary<TKey,TValue>implementację napisaną w języku innym niż C #, nawet jeśli MyStruct s = new MyStruct(myDictionary);wygląda jak przypisanie, może potencjalnie pozostać sniezmodyfikowana.

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 outparametry 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.


0

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.


-3

Pamiętaj, że parametr referencyjny przekazywany wewnątrz funkcji jest przetwarzany bezpośrednio.

Na przykład,

    public class MyClass
    {
        public string Name { get; set; }
    }

    public void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog".
    }

    public void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

To napisze Pies, nie Kot. Dlatego powinieneś bezpośrednio pracować na someObject.


6
Chociaż wszystko tutaj jest w zasadzie prawdą, tak naprawdę nie wyjaśnia różnicy między wartością przez odniesienie lub na zewnątrz. W najlepszym razie połowa wyjaśnia różnicę między typem odniesienia a wartością / niezmiennym typem.
Conrad Frix,

Jeśli chcesz, aby ten kod napisał cat, przekaż ten obiekt wraz z kluczem „ref” w następujący sposób: public static void Bar (ref MyClass someObject), Bar (ref myObject);
Daniel Botero Correa

-4

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?

        string a = "Hello";

        string b = "goodbye";

        b = a; //attempt to make b point to a, won't work.

        a = "testing";

        Console.WriteLine(b); //this will produce "hello", NOT "testing"!!!!

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.


5
Witamy w Stack Overflow, Edwin. O ile wiem, ciągi znaków są przekazywane przez odwołanie, tak jak każdy inny obiekt. Możesz się mylić, ponieważ ciągi są niezmiennymi obiektami, więc nie jest tak oczywiste, że są przekazywane przez referencję. Wyobraź sobie, że ciąg ma wywoływaną metodę Capitalize(), która zmieniłaby jego zawartość na wielkie litery. Jeśli następnie zastąpić linię a = "testing";z a.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ść.
Don Kirkby,

2
Istnieją trzy podstawowe typy semantyki, które typ może ujawnić: semantyka zmiennych zmiennych, semantyka zmiennych wartości i semantyka niezmienna. Rozważ zmienne xiy typu T, które mają pole lub właściwość m, i załóż, że x jest skopiowane do y. Jeśli T ma semantykę odniesienia, zmiany w xm będą obserwowane przez ym. Jeśli T ma semantykę wartości, można zmienić xm bez wpływu na ym. Jeśli T ma niezmienną semantykę, ani xm, ani ym nigdy się nie zmienią. Niezmienna semantyka może być symulowana przez obiekty odniesienia lub wartości. Ciągi są niezmiennymi obiektami odniesienia.
supercat
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.