poniedziałek, 17 sierpnia 2020

Przesyłanie argumentów przez referencje

Temat dobrze znany, ale wraz z rozwojem C# zwłaszcza siódmej i ósmej wersji postanowiłem ponownie podejść do tematu. 

Przed wprowadzeniem Tupli w siódmej wersji języka, aby zwrócić więcej niż jedną wartość z metody mogliśmy zastosować słowo kluczowe out. Ponieważ słowo kluczowe out istnieje od początku języka C# możemy się z nim spotkać w wielu miejscach - zwłaszcza przy próbach konwersji:

            //1. Parsing string to int
            string someText = "5";
            bool canParse = int.TryParse(someText, out int a);
            if (canParse)
            {
                Console.WriteLine($"The value is {a}");
            }
            else
            {
                Console.WriteLine("Can't parse");
            }

            //2. Try get value from dictionary
            var data = new Dictionary<string, int>
            {
                ["Adam"] = 30,
                ["Sebastian"] = 50,
                ["Marek"] = 35
            };
            string searchKey = "Adam";
            var canGetValueByKey = data.TryGetValue(searchKey, out int age);
            if(canGetValueByKey)
            {
                Console.WriteLine($"{searchKey} is {age}");
            }
            else
            {
                Console.WriteLine("Key not present in dictionary");
            }

Sama deklaracja funkcji Parse dla typu int wygląda następująco:

        public static bool TryParse(String s, out Int32 result) 
        {
            return Number.TryParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result);
        }

Zarówno w funkcji jak i jej wywołaniu musimy jawne podać słówko out

Warto w tym miejscu zaznaczyć, że metody oznaczone słówkiem kluczowym async nie mogą posiadać argumentów out. Jest to spowodowane faktem, że metody async nie determinują jednoznacznie kiedy zostaną wykonane w pełni. Metoda może wykonać się w części i zwrócić kontrolę do głównej funkcji, która z kolei także może zwrócić kontrolę do metody wołającej. Tym sposobem zmienne przekazane przez out mogłyby już nie istnieć. Ta sama zależność jest związana z funkcjami anonimowymi, do których także nie możemy przesłać parametru za pomocą słówka out.

Czasami może się zdarzyć, że wartość parametru out nie jest nam potrzebna. Przykładowo chcemy tylko sprawdzić czy wartość można skonwertować czy też nie. Jeżeli nie interesuje nas w takim przypadku wartość możemy zastosować tzw. discard operator:

            string someText = "5";
            bool canParse = int.TryParse(someText, out _);

Przed erą C# 7.0 musielibyśmy zadeklarować zmienną, której wartość byłaby po prostu nie używana. 


Drugą metodą przekazywania zmiennych przez referencję jest użycie słówka ref. Działa podobnie jak słówko out z tą różnicą, że zmienna przesyłana do metody musi zostać zainicjowana przed wysłaniem:

        static async Task Main(string[] args)
        {
            int value = 20;
            DoSomething(ref value);
            Console.WriteLine(value);
        }

        public static void DoSomething(ref int value)
        {
            value = 10;
        }


Trzecią opcją jest nowe słówko (począwszy od C# 7.2) in. Słówko to pozwala na przesłanie obiektu przez referencję ale tylko do odczytu. Zachodzi tu relacja out - wysyłamy zmienną aby ją nadpisać, in - zmienna tylko do odczytu. Można zapytać po co wprowadzono słówko in? Na pewno nie przyda się ono do przesłania obiektów typu int. Obiekty wartościowe (np. duże struktury) przesyłane są przez wartość, czyli krótko mówiąc przesyłamy ich kopię do metody. Kopiowanie dużej ilości obiektów może obniżyć wydajność naszej aplikacji. In pozwala uniknąć kopiowania - wszak prześlemy kopię referencji (32 lub 64 bity):

        static async Task Main(string[] args)
        {
            var point3D = new Point3D(1, 2, 3);
            DoSomething(point3D);
        }

        public static void DoSomething(in Point3D point)
        {
            Console.WriteLine(point);
            
            //point = new Point3D(3, 4, 5); //Error CS8331  Cannot assign to variable 'in Point3D' because it is a readonly variable
        }
    }

    public readonly struct Point3D
    {
        public double X { get; }
        public double Y { get; }
        public double Z { get; }

        public Point3D(double x, double y, double z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        public override string ToString()
        {
            return $"X:{X} Y:{Y} Z:{Z}";
        }
    }

Należy zauważyć, że struktura została oznaczona jako readonly. Jeżeli by nie została, kompilator przyjąłby agresywniejszą metodę i stworzył mimo wszystko kopię struktury Point3d.

W kolejnej części zajmę się referencjami do zmiennych i zwracanych wyników. 

Brak komentarzy:

Prześlij komentarz