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";
}

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"

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]);

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


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.


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.


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();
}

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;
    }
}

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
}

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.


-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

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.