Przejdź do głównej zawartości

[C#] Czym się różni ref od out? Jak i kiedy się je stosuje?


Parametr przekazany z modyfikatorem ref:
  • musi mieć zainicjalizowaną wartość przed przekazaniem do metody
  • metoda może go odczytać i zmodyfikować
  • używamy, kiedy chcemy aby funkcja modyfikowała parametr przez referencję, a nie przez wartość
Parametr przekazany z modyfikatorem out:
  • nie musi być zainicjalizowany przed przekazaniem do metody. Nawet jeśli był zainicjalizowany, metoda zignoruje jego wartość
  • musi zostać zainicjalizowany w ciele metody
  • używamy, kiedy chcemy zwrócić dodatkowe dane z funkcji (w C# funkcja może być void lub zwracać jeden parametr - nigdy więcej)

Przyjrzyjmy się bliżej jak działa modyfikator ref:

    class TestClass
    {
        public void TestMethod(ref int a)
        {
            a = 6;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var testObject = new TestClass();
            int x = 5;
            testObject.TestMethod(ref x);
            Console.WriteLine(x); //wypisze się 6

            Console.ReadKey();
        }
    }

Modyfikator ten sprawia więc, że value type traktowany jest w sposób podobny do reference type - do funkcji nie jest przekazywana kopia zmiennej, lecz referencja do niej. Dla zwykłego obiektu program zadziałałby podobnie:

    class NumberBox
    {
        public int A;
    }

    class TestClass
    {
        public void TestMethod(NumberBox numberBox)
        {
            numberBox.A = 6;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var testObject = new TestClass();
            var numberBox = new NumberBox { A = 5 };
            testObject.TestMethod(numberBox);
            Console.WriteLine(numberBox.A); 
            //wypisze się 6, ponieważ do funkcji obiekty przekazywane są przez referencję

            Console.ReadKey();
        }
    }

Bez słowa kluczowego ref kod dzialalby w ten sposób:

    class TestClass
    {
        public void TestMethod(int a)
        {
            a = 6;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var testObject = new TestClass();
            int x = 5;
            testObject.TestMethod(x);
            Console.WriteLine(x); 
            //wypisze się 5, ponieważ do funkcji przekazana została kopia x

            Console.ReadKey();
        }
    }

Korzystając ze słowa kluczowego ref musimy uważać na popularne błędy, które sprawią, że program się nie skompiluje:

    class TestClass
    {
        public void TestMethod(ref int a)
        {
            a = 6;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var testObject = new TestClass();
            testObject.TestMethod(ref 5);
            //nie skompiluje się - musimy przekazać zmienną do której można przypisać wartość

            int x;
            testObject.TestMethod(ref x);
            //nie skompiluje się - x nie jest zainicjalizowane

            x = 10;
            testObject.TestMethod(x);
            //nie skompiluje się - brakuje słowa kluczowego ref

            testObject.TestMethod(ref x); //ok

            Console.ReadKey();
        }

Nieco inaczej sprawa ma się z modyfikatorem out:

    class TestClass
    {
        public void TestMethod(out int a)
        {
            a = 10;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var testObject = new TestClass();

            int x;
            testObject.TestMethod(out x);
            Console.WriteLine(x); //wypisze się 10

            Console.ReadKey();
        }
    }

Tutaj także trzeba uważać na błędy kompilacji, zarówno przy definiowaniu funkcji z parametrem out...

    class TestClass
    {
        public void TestMethod(out int a)
        //nie skompiluje się - musimy ustawić wartość a przed wyjściem z funkcji
        {
        }
    }

...jak i przy korzystaniu z niej:

    class TestClass
    {
        public void TestMethod(out int a)
        {
            a = 10;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var testObject = new TestClass();
            
            testObject.TestMethod(out 5);
            //podobnie jak z ref
            //nie skompiluje się - musimy przekazać zmienną do której można przypisać wartość

            int x;
            testObject.TestMethod(out x); //ok

            Console.ReadKey();
        }
    }

Parametr przekazany z modyfikatorem out może mieć wartość zainicjalizowaną przed przekazaniem do funkcji - jednak funkcja tę wartość zignoruje i będzie traktowała parametr jako nieprzypisany oraz bezwzględnie wymagała jego zdefiniowanie przed wyjściem z funkcji:

    class TestClass
    {
        public void TestMethod(out int a)
        {
            Console.WriteLine(a);//błąd kompilacji - zmienna jest nieprzypisana
            a = 10;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var testObject = new TestClass();

            int x = 10;
            testObject.TestMethod(out x);

            Console.ReadKey();
        }
    }

Zastanówmy się jeszcze nad przykładowymi sytuacjami, kiedy chcemy użyć tych słów kluczowych:

Ref może nam się przydać, kiedy chcemy mieć licznik jakichś operacji. Załóżmy, że mamy funkcję SaveMember, której zadaniem jest dodanie danych użytkownika do bazy danych. Może się okazać, że zajdą jakieś okoliczności, w których uczestnik nie zostanie dodany. Jednocześnie chcemy zachować licznik uczestników, których udało się dodać.

    class MembersSaver
    {
        private Random _random = new Random();

        public void TrySave(ref int counter, string name)
        {
            if(ShallBeSaved(name))
            {
                Save(name);
                ++counter;
            }
        }

        private bool ShallBeSaved(string name)
        {
            return GetRandomBool();
        }

        private void Save(string name)
        {
            Console.WriteLine($"Zapisywanie {name} do bazy danych");
        }

        private bool GetRandomBool()
        {
            return _random.Next(2) == 0;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var membersSaver = new MembersSaver();
            int counter = 0;
            membersSaver.TrySave(ref counter, "Janusz");
            membersSaver.TrySave(ref counter, "Mariusz");
            membersSaver.TrySave(ref counter, "Dariusz");
            Console.WriteLine($"Zapisano {counter} uczestników do bazy");

            Console.ReadKey();
        }
    }

Naturalnie istnieją sposoby na zrealizowanie takiej logiki bez uzycia słowa kluczowego ref.

Jeśli zaś idzie o słowo kluczowe out, pozwala nam ono na zwrócenie dodatkowych danych z funkcji - tak jakbyśmy zwracali nie jedną, a więcej wartości. Przykładem mogą być wbudowane w .NET funkcje parsujące, np int.TryParse. W funkcji tej chcemy zwrócić dwie informacje - czy udało się sparsować stringa i jaka jest sparsowana wartość.

    class Program
    {
        static void Main(string[] args)
        {
            int a;
            var didParseASucceeded = int.TryParse("11", out a);
            Console.WriteLine($"Czy udało się sparsować a: {didParseASucceeded}, sparsowana wartość to {a}");

            int b;
            var didParseBSucceeded = int.TryParse("abc", out b);
            Console.WriteLine($"Czy udało się sparsować b: {didParseBSucceeded}, sparsowana wartość to {b}");

            Console.ReadKey();
        }
    }

Komentarze