Po co używać słowa kluczowego „ref” podczas przekazywania obiektu?

290

Jeśli przekazuję obiekt do metody, dlaczego powinienem używać słowa kluczowego ref? Czy to zresztą zachowanie domyślne?

Na przykład:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

Dane wyjściowe to „Bar”, co oznacza, że ​​obiekt został przekazany jako odniesienie.

Ryan
źródło

Odpowiedzi:

299

Podaj a, refjeśli chcesz zmienić obiekt:

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

Po wywołaniu DoSomething tnie odnosi się do oryginału new TestRef, ale odnosi się do zupełnie innego obiektu.

Może to być również przydatne, jeśli chcesz zmienić wartość niezmiennego obiektu, np string. Nie można zmienić wartości stringraz utworzonej. Ale używając a ref, możesz utworzyć funkcję, która zmienia łańcuch na inną, która ma inną wartość.

Edycja: jak wspomnieli inni ludzie. Nie jest dobrym pomysłem używanie, refchyba że jest to potrzebne. Użycie refdaje tej metodzie swobodę zmiany argumentu na coś innego, wywołujące metodę będą musiały zostać zakodowane, aby upewnić się, że poradzą sobie z tą możliwością.

Ponadto, gdy typ parametru jest obiektem, wówczas zmienne obiektu zawsze działają jako odniesienia do obiektu. Oznacza to, że po użyciu refsłowa kluczowego masz odniesienie do odwołania. Umożliwia to wykonywanie czynności opisanych w powyższym przykładzie. Ale jeśli typ parametru jest pierwotną wartością (np. int), To jeśli parametr ten jest przypisany do metody, wartość przekazanego argumentu zostanie zmieniona po zwróceniu metody:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}
Scott Langham
źródło
88

Musisz rozróżnić „przekazywanie referencji przez wartość” i „przekazywanie parametru / argumentu przez referencję”.

Napisałem dość długi artykuł na ten temat, aby uniknąć konieczności ostrożnego pisania za każdym razem, gdy pojawia się na grupach dyskusyjnych :)

Jon Skeet
źródło
1
Dobrze napotkałem problem podczas aktualizacji VB6 do kodu .Net C #. Istnieją sygnatury funkcji / metod, które pobierają parametry ref, out i zwykłe. Jak więc lepiej odróżnić różnicę między zwykłym paramem a referencją?
bonCodigo
2
@bonCodigo: Nie jestem pewien, co rozumiesz przez „lepsze rozróżnianie” - to część podpisu, a także musisz określić refna stronie połączenia… gdzie jeszcze chcesz, żeby to było wyróżnione? Semantyka jest również dość jasna, ale należy ją wyrazić ostrożnie (zamiast „obiektów przekazuje się przez odniesienie”, co jest powszechnym nadmiernym uproszczeniem).
Jon Skeet
nie wiem, dlaczego studio wizualne nadal nie pokazuje wprost, co jest przekazywane
MonsterMMORPG
3
@MonsterMMORPG: Obawiam się, że nie wiem, co masz na myśli.
Jon Skeet,
57

W .NET, gdy przekazujesz dowolny parametr do metody, tworzona jest kopia. W typach wartości oznacza, że ​​wszelkie modyfikacje wartości są w zakresie metody i są tracone po wyjściu z metody.

Przy przekazywaniu typu odniesienia tworzona jest również kopia, ale jest to kopia odniesienia, tj. Teraz masz DWA odniesienia w pamięci do tego samego obiektu. Jeśli więc użyjesz odwołania do zmodyfikowania obiektu, zostanie on zmodyfikowany. Ale jeśli zmodyfikujesz sam odnośnik - musimy pamiętać, że jest to kopia - wtedy wszelkie zmiany zostaną utracone po wyjściu z metody.

Jak powiedziano wcześniej, przypisanie jest modyfikacją odwołania, a zatem zostaje utracone:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

Powyższe metody nie modyfikują oryginalnego obiektu.

Mała modyfikacja twojego przykładu

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }
Ricardo Amores
źródło
6
Podoba mi się ta odpowiedź bardziej niż odpowiedź zaakceptowana. Wyjaśnia jaśniej, co dzieje się przy przekazywaniu zmiennej typu referencyjnego ze słowem kluczowym ref. Dziękuję Ci!
Stefan
17

Ponieważ TestRef jest klasą (które są obiektami referencyjnymi), możesz zmienić zawartość wewnątrz t bez przekazywania jej jako referencji. Jeśli jednak podasz t jako odwołanie, TestRef może zmienić to, do czego odnosi się pierwotne t. tzn. wskaż inny obiekt.

Ferruccio
źródło
16

Dzięki refniemu możesz napisać:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

I t zostanie zmienione po zakończeniu metody.

Rinat Abdullin
źródło
8

Pomyśl o zmiennych (np. foo) Typów referencyjnych (np. List<T>), Które zawierają identyfikatory obiektów w postaci „Obiekt # 24601”. Załóżmy, że instrukcja foo = new List<int> {1,5,7,9};powoduje fooprzechowywanie „Object # 24601” (lista z czterema elementami). Następnie wywołanie foo.Lengthzapyta Obiekt # 24601 o jego długość i odpowie 4, więc foo.Lengthbędzie równa 4.

Jeśli foozostanie przekazany do metody bez użycia ref, metoda ta może wprowadzić zmiany w obiekcie nr 24601. W wyniku takich zmian foo.Lengthmoże już nie być równy 4. Jednak sama metoda nie będzie mogła się zmienić foo, co nadal będzie zawierać „Obiekt # 24601”.

Przekazanie foojako refparametru pozwoli wywoływanej metodzie wprowadzić zmiany nie tylko do obiektu # 24601, ale także do foosamego siebie. Metoda może utworzyć nowy obiekt # 8675309 i zapisać w nim odwołanie foo. Jeśli tak się stanie, foonie będzie już zawierał „Object # 24601”, ale „Object # 8675309”.

W praktyce zmienne typu odniesienia nie zawierają ciągów znaków „Obiekt # 8675309”; nawet nie przechowują niczego, co można znacząco przekształcić w liczbę. Mimo że każda zmienna typu referencyjnego będzie zawierać pewien wzorzec bitowy, nie ma stałej zależności między wzorami bitowymi przechowywanymi w takich zmiennych a obiektami, które identyfikują. W żaden sposób kod nie może wyodrębnić informacji z obiektu lub odwołania do niego, a następnie ustalić, czy inne odwołanie zidentyfikowało ten sam obiekt, chyba że kod przechowywał lub wiedział o odwołaniu, które zidentyfikowało obiekt oryginalny.

supercat
źródło
5

To jest jak przekazanie wskaźnika do wskaźnika w C. W .NET pozwoli ci to zmienić to, o czym mówi oryginalny T, choć osobiście uważam, że jeśli robisz to w .NET, prawdopodobnie masz problem z projektem!

pezi_pink_squirrel
źródło
3

Używając refsłowa kluczowego z typami referencji, skutecznie przekazujesz referencję do referencji. Na wiele sposobów jest to to samo, co użycie outsłowa kluczowego, ale z tą niewielką różnicą, że nie ma gwarancji, że metoda faktycznie przypisze cokolwiek do refparametru ed.

Isak Savo
źródło
3

ref naśladuje (lub zachowuje się) jako obszar globalny tylko dla dwóch zakresów:

  • Gość
  • Callee.
guneysus
źródło
1

Jeśli jednak podajesz wartość, rzeczy wyglądają inaczej. Możesz wymusić przekazanie wartości przez referencję. Dzięki temu możesz na przykład przekazać liczbę całkowitą do metody i pozwolić metodzie zmodyfikować liczbę całkowitą w Twoim imieniu.

Andrzej
źródło
4
Niezależnie od tego, czy przekazujesz odwołanie, czy wartość typu wartości, domyślnym zachowaniem jest przekazywanie wartości. Musisz tylko zrozumieć, że w przypadku typów referencji przekazywana wartość jest referencją. To nie to samo, co przekazywanie przez referencję.
Jon Skeet,
1

Ref oznacza, czy funkcja może dostać się w ręce samego obiektu, czy tylko jego wartości.

Przekazywanie przez odniesienie nie jest związane z językiem; jest to strategia wiązania parametrów obok parametru pass-by-value, pass by name, pass by need itp ...

Krótka informacja: nazwa klasy TestRefjest w tym kontekście okropnie złym wyborem;).

xtofl
źródło