Jakie jest zastosowanie „ref” dla zmiennych typu referencyjnego w C #?

176

Rozumiem, że jeśli mijam typu wartość ( int, struct, itd.) Jako parametr (bez refhasła), kopia tej zmiennej jest przekazywany do metody, ale jeśli mogę użyć refsłowa kluczowego odniesienie do tej zmiennej jest przekazywana, nie nowy.

Ale w przypadku typów referencyjnych, takich jak klasy, nawet bez refsłowa kluczowego, referencja jest przekazywana do metody, a nie kopia. Więc jakie jest zastosowanie refsłowa kluczowego z typami referencyjnymi?


Weź na przykład:

var x = new Foo();

Jaka jest różnica między następującymi?

void Bar(Foo y) {
    y.Name = "2";
}

i

void Bar(ref Foo y) {
    y.Name = "2";
}
Andreas Grech
źródło

Odpowiedzi:

154

Możesz zmienić foopunkty do używania y:

Foo foo = new Foo("1");

void Bar(ref Foo y)
{
    y = new Foo("2");
}

Bar(ref foo);
// foo.Name == "2"
user7116
źródło
17
więc w zasadzie otrzymujesz odniesienie do oryginalnego odniesienia
lhahne,
2
Możesz zmienić to, do czego „odnosi się” oryginalne odniesienie, więc tak.
user7116
1
Chris, twoje wyjaśnienie jest świetne; Dziękuję za pomoc w zrozumieniu tej koncepcji.
Andreas Grech
4
Czyli użycie „ref” na obiekcie jest jak użycie podwójnych wskaźników w C ++?
Tom Hazel
1
@TomHazel: -ish , pod warunkiem, że używasz „podwójnych” wskaźników w C ++, aby zmienić to, na co wskazuje wskaźnik.
user7116
29

Istnieją przypadki, w których chcesz zmodyfikować rzeczywiste odniesienie, a nie wskazany obiekt:

void Swap<T>(ref T x, ref T y) {
    T t = x;
    x = y;
    y = t;
}

var test = new[] { "0", "1" };
Swap(ref test[0], ref test[1]);
Mehrdad Afshari
źródło
21

Jon Skeet napisał świetny artykuł o przekazywaniu parametrów w C #. Wyszczególnia dokładnie dokładne zachowanie i użycie przekazywania parametrów przez wartość, przez odwołanie ( ref) i przez wyjście ( out).

Oto ważny cytat z tej strony w odniesieniu do refparametrów:

Parametry referencyjne nie przekazują wartości zmiennych używanych w wywołaniu elementu członkowskiego funkcji - używają samych zmiennych. Zamiast tworzyć nowe miejsce przechowywania zmiennej w deklaracji elementu członkowskiego funkcji, używana jest ta sama lokalizacja przechowywania, więc wartość zmiennej w elemencie członkowskim funkcji i wartość parametru odwołania będą zawsze takie same. Parametry referencyjne wymagają modyfikatora ref jako części zarówno deklaracji, jak i wywołania - oznacza to, że zawsze jest jasne, gdy przekazujesz coś przez referencję.

Noldorin
źródło
11
Podoba mi się analogia przekazywania psiej smyczy przyjacielowi za przekazanie wartości referencyjnej ... jednak szybko się psuje, ponieważ myślę, że prawdopodobnie zauważysz, gdyby twój przyjaciel wymienił twoje shitzu na dobermana, zanim ci odda smycz ;-)
corlettk
16

Bardzo ładnie wyjaśniono tutaj: http://msdn.microsoft.com/en-us/library/s6938f28.aspx

Streszczenie z artykułu:

Zmienna typu referencyjnego nie zawiera bezpośrednio swoich danych; zawiera odniesienie do swoich danych. Gdy przekazujesz parametr typu odwołania według wartości, można zmienić dane wskazywane przez odwołanie, takie jak wartość elementu członkowskiego klasy. Nie możesz jednak zmienić wartości samego odwołania; oznacza to, że nie można użyć tego samego odwołania do przydzielenia pamięci dla nowej klasy i utrzymywać ją poza blokiem. Aby to zrobić, przekaż parametr za pomocą słowa kluczowego ref lub out.

himanshupareek66
źródło
4
Wyjaśnienie jest rzeczywiście bardzo miłe. Jednak odpowiedzi zawierające tylko łącze są odradzane w SO. Dla wygody czytelników dodałem streszczenie z artykułu.
Marcel,
10

Gdy przekazujesz typ odwołania za pomocą słowa kluczowego ref, przekazujesz odwołanie przez odwołanie, a wywoływana metoda może przypisać nową wartość do parametru. Ta zmiana zostanie przeniesiona na zakres wywołujący. Bez ref odwołanie jest przekazywane przez wartość i tak się nie dzieje.

C # ma również słowo kluczowe „out”, które jest bardzo podobne do ref, z tą różnicą, że w przypadku „ref” argumenty muszą zostać zainicjowane przed wywołaniem metody, a bez „out” należy przypisać wartość w metodzie odbierającej.

Rytmis
źródło
5

Pozwala na modyfikację przekazanej referencji. Np

void Bar()
{
    var y = new Foo();
    Baz(ref y);
}

void Baz(ref Foo y)
{
    y.Name = "2";

    // Overwrite the reference
    y = new Foo();
}

Możesz również użyć , jeśli nie obchodzi Cię przekazana referencja:

void Bar()
{
    var y = new Foo();
    Baz(out y);
}

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}
Hans Van Slooten
źródło
4

Kolejny kod

class O
{
    public int prop = 0;
}

class Program
{
    static void Main(string[] args)
    {
        O o1 = new O();
        o1.prop = 1;

        O o2 = new O();
        o2.prop = 2;

        o1modifier(o1);
        o2modifier(ref o2);

        Console.WriteLine("1 : " + o1.prop.ToString());
        Console.WriteLine("2 : " + o2.prop.ToString());
        Console.ReadLine();
    }

    static void o1modifier(O o)
    {
        o = new O();
        o.prop = 3;
    }

    static void o2modifier(ref O o)
    {
        o = new O();
        o.prop = 4;
    }
}
Svend
źródło
3

Oprócz istniejących odpowiedzi:

Jak prosiłeś o różnicę między dwiema metodami: Nie ma co (ntra) wariancji podczas używania reflub out:

class Foo { }
class FooBar : Foo { }

static void Bar(Foo foo) { }
static void Bar(ref Foo foo) { foo = new Foo(); }

void Main()
{
    Foo foo = null;
    Bar(foo);           // OK
    Bar(ref foo);       // OK

    FooBar fooBar = null;
    Bar(fooBar);        // OK (covariance)
    Bar(ref fooBar);    // compile time error
}
springy76
źródło
1

Parametr w metodzie wydaje się zawsze przekazywać kopię, pytanie jest kopią czego. Kopia jest wykonywana przez konstruktor kopiujący dla obiektu, a ponieważ wszystkie zmienne są Object w C #, wierzę, że tak jest w przypadku wszystkich z nich. Zmienne (obiekty) są jak ludzie mieszkający pod niektórymi adresami. Albo zmieniamy osoby mieszkające pod tymi adresami, albo możemy utworzyć więcej odniesień do osób mieszkających pod tymi adresami w książce telefonicznej (rób płytkie kopie). Tak więc więcej niż jeden identyfikator może odnosić się do tego samego adresu. Typy referencyjne wymagają więcej miejsca, więc w przeciwieństwie do typów wartości, które są bezpośrednio połączone strzałką z ich identyfikatorem w stosie, mają wartość dla innego adresu w stercie (większa przestrzeń do zamieszkania). To miejsce należy zabrać ze stosu.

Typ wartości: Indentifier (zawiera wartość = adres wartości stosu) ----> Wartość typu wartości

Typ odniesienia: Identyfikator (zawiera wartość = adres wartości stosu) ----> (zawiera wartość = adres wartości sterty) ----> Wartość sterty (najczęściej zawiera adresy do innych wartości), wyobraź sobie więcej strzałek wbijających się w różne kierunki do Array [0], Array [1], array [2]

Jedynym sposobem zmiany wartości jest podążanie za strzałkami. Jeśli jedna strzałka zostanie zgubiona / zmieniona w sposób, w jaki wartość jest nieosiągalna.

Petar Drianov
źródło
-1

Zmienne referencyjne przenoszą adres z jednego miejsca do drugiego, więc każda aktualizacja ich w dowolnym miejscu będzie odzwierciedlać wszystkie miejsca, WTEDY, jakie jest zastosowanie REF. Zmienna referencyjna (405) jest dobra, dopóki żadna nowa pamięć nie zostanie przydzielona zmiennej referencyjnej przekazanej w metodzie.

Po przydzieleniu nowej pamięci (410) zmiana wartości tego obiektu (408) nie będzie wszędzie widoczna. Przychodzi ten ref. Ref jest odniesieniem do odniesienia, więc za każdym razem, gdy alokuje nową pamięć, należy ją poznać, ponieważ wskazuje na tę lokalizację, dlatego wartość może być współdzielona przez everyOne. Możesz zobaczyć obraz dla większej przejrzystości.

Odniesienie do zmiennej odniesienia

srbh
źródło