wtorek, 18 sierpnia 2020

Referencje do zmiennych i zwracanych wartości

Poprzedni artykuł omówił sposoby przekazywania przez referencję argumentów do metod. Teraz zobaczmy w jaki sposób możemy uzyskać referencję do zmiennych oraz wyników metod. 

            string s1 = "Ala ma kota";
            ref string ref1 = ref s1;

            ref1 = "Override value";
            Console.WriteLine(s1);

Jakie są ograniczenia lokalnych referencji?
  • Po pierwsze kompilator nie pozwoli stworzyć referencji do zmiennej, która zostanie usunięta przed referencją (zakres widzialności zmiennej i referencji musi być taki sam).
  • Nie można stosować ref dla metod asynchronicznych (async)
  • nie można używać w iteratorach
  • nie można używać w funkcjach anonimowych

Warto jednak zaznaczyć, że ref można zastosować dla właściwości. Dzieje się tak dlatego, że właściwości są przekształcane do metod get i set. Generalna zasada jest taka, że kompilator musi mieć pewność że referencja nie "przeżyje" obiektu do którego ta referencja się odnosi.

Zobaczmy teraz kilka przykładów:

    public class Sample
    {
        public ref int WillFail()
        {
            int i = 42;
            return ref i;
        }
    }
Kod ten nie skompiluje się gdyż, zmienna i przestanie istnieć po opuszczeniu zasięgu metody WillFail.

    public class Sample
    {
        public ref int GetSameRef(ref int arg) => ref arg;

        public ref int WillFail()
        {
            int i = 42;
            return ref GetSameRef(ref i);
        }
    }

Kod ten także nie zadziała. Próbujemy poprzez dodatkową funkcję zwrócić referencję do zmiennej i która po opuszczeniu zasięgu metody WillFail zostanie usunięta. 

    public class Sample
    {
        public ref int GetSameRef(ref int arg) => ref arg;

        public ref int WillWork(ref int i)
        {
            return ref GetSameRef(ref i);
        }
    }

Ten przypadek zadziała poprawnie. Zwracamy referencję do zmiennej, która zostanie przekazana z zewnątrz a więc "przeżyje" lokalny zasięg. 

    public class Sample
    {
        private int i;
        private int[] tab = new int[5];
        public ref int ReferenceToField => ref i;
        public ref int ReferenceToArrayElement(int index) => ref tab[index];
    }

Powyższy przykład także będzie kompilował się poprawnie - zostanie zwrócona referencja do pola oraz elementu tablicy. Jest to możliwe dlatego, że składowe klasy żyją na stosie i garbage collector jest świadomy, aby ich nie usuwać. 

Jaka jest realna zaleta używania ref dla lokalnych zmiennych i wartości zwracanych? Tak samo jak w przypadku przesyłania dużych struktur danych możemy zaoszczędzić moc, która byłaby marnowana na kopiowanie obiektów. 

Brak komentarzy:

Prześlij komentarz