Categories
C#

Czarne jak konsola

Wpis [part not set] z 8 w serii C# - podstawy

Jeżeli po poprzednim poście męczyło Cię pytanie “po co właściwie zainstalowałem to Visual Studio?” to już piszę odpowiedź. Chociaż, w sumie, wolę żebyś sam to sprawdził – uruchom proszę na Twoim komputerze Visual Studio 2022. Po prawej stronie ekranu startowego znajdziesz przycisk Create a new project – wskaż go.

Pora na wybranie szablonu projektu. Kurczę, czego? Visual Studio oferuje Ci szereg predefiniowanych “rusztowań” projektów, które możesz zapełniać potrzebną Ci zawartością. Naszą przygodę zaczniemy od aplikacji konsolowej, czyli czegoś co spełnia poniższe cechy:

  • jest kuszące niczym Polski Ład,
  • ma rozszerzenie .exe, jest executable, czyli samowykonywalne,
  • oferuje podstawową interakcję z użytkownikiem, wyświetlając znaki na ekranie oraz przyjmując wartości od operatora,
  • standardowo ma przepiekny, głęboki, czarny kolor tła 😛

W polu wyszukiwania wpisz console app i wybierz wariant dla C# w .NET Framework.

Jeżeli dziwisz się dlaczego są dwa warianty templatu dla aplikacji konsolowej w C# to na ten moment wiedz, że wybieramy starszy sposób, z którego korzystają wszystkie programy budowlane. Jak to zwykle bywa – innowacje przybywają do nas z ‘lekkim’ opóźnieniem.

Ok, wybrałeś Next, podałeś nazwę projektu (np. PierwszaKonsola), jego lokalizację i kliknałeś w Create. Po chwili zwątpienia widzisz w pełnej krasie swój pierwszy projekt w C# – brawo Ty! Pora uruchomić program, naciśnij zachęcający, zielony grot z napisem Start.

Hmm, u Ciebie też coś czarnego pojawiło się na ekranie i szybko zniknęło? Spójrz na zawartość naszego programu, czyli obszar pomiędzy klamerkami między 12 a 13 linijką. Niczego tam nie ma i to z tego powodu nasz program bardzo szybko kończy pracę. Wpisz proszę jakieś polecenie, np. próbę wyświetlenia na ekranie tekstu, o choćby tego:

        static void Main(string[] args)
        {
            Console.WriteLine("Let's construct IT");
        }

Naciśnij znów zielony grot – chcemy sprawdzić jak teraz zadziała nasz program. Kurczę, znowu okno pojawia się i znika, a to przecież rola magika. Czegoś tu brakuje, jakiejś formy oczekiwania na interakcję użytkownika. Zmodyfikuj wnętrze metody Main, tak żeby poczekała na ruch operatora, coś na kształt tego:

    static void Main(string[] args)
    {
        Console.WriteLine("Let's construct IT");
        Console.ReadLine();
    }

Uruchom program (F5 jest skrótem dla zielonego grota, będzie szybciej) – jeeeeeest, działa! To jest moment na zawołanie żony/męża/mamy/kota i pokazanie, że potrafisz programować, a przynajmniej jesteś na dobrej drodze.

Żeby zamknąć naszą aplikację możesz użyć krzyżyka lub wykonać to, czego oczekuje linijka Console.ReadLine() czyli wpisać jakiekolwiek znaki i nacisnąć enter.

Pamiętasz kod obliczający moment bezwładności prostokąta z wcześniejszych postów? Nie? Ja też nie pamiętałem, dlatego wklejam go poniżej. Proszę skopiuj go i wklej u siebie, zastąpując to wszystko co widzisz w edytorze Visual Studio.

using System;

public class Program
{
    public static void Main()
    {
        double momentOfInertia1 = GetMomentOfInertia(30, 60);
        double momentOfInertia2 = GetMomentOfInertia(20, 50);

        string message1 = string.Format("Moment bezwładności 1 wynosi {0:N2}", momentOfInertia1);
        Console.WriteLine(message1);
        string message2 = string.Format("Moment bezwładności 2 wynosi {0:N2}", momentOfInertia2);
        Console.WriteLine(message2);

        Console.ReadLine();
    }

    public static double GetMomentOfInertia(double b, double h)
    {
        double momentOfInertia = (b * Math.Pow(h, 3)) / 12;

        return momentOfInertia;
    }
}

Uruchom nowy program, najlepiej klawiszem F5. W konsoli wyświetlone zostały dwie wartości momentu bezwładności – nasz program działa.

Gdy przedstawiałem ten przykład w Fiddle wspomniałem, że posiada on zaszyte na sztywno wartości dla których wykonane mają zostać działania. Chcemy tworzyć programy elastyczne, skrojone pod potrzeby użytkowników i właśnie teraz nadszedł czas na implementację nowej funkcji – pobierania od operatora szerokości i wysokości prostokąta.

Na samym początku metody Main dopisz na szybko linijkę Console.ReadLine(); i skieruj kursor myszy na słowo ReadLine – Visual Studio podpowie Ci co robi ta metoda oraz jaki typ zwraca. W tym przypadku jest to string, czyli słowo/zestaw znaków wpisany przez użytkownika w oknie konsoli i zatwierdzony naciśnięciem Enter.

Wykorzystajmy tę wiedzę i przypiszmy to co poda użytkownik do nowej zmiennej, żeby było intuicyjnie nazwiemy ją widthFromUser. Podobnie postąpmy z wysokością przekroju – heightFromUser.

Pobranie wartości od użytkownika i przypisanie ich do dwóch nowych zmiennych w żaden sposób nie wpłynęło na argumenty metody GetMomentOfInertia – one dalej są przypisane na sztywno. Pora to zmienić – zróbmy coś szalonego!

Wykorzystałem nowe zmienne aplikując je jako argumenty naszej metody. Skończyło się to przepiękną czerwoną falą wskazującą, że coś w tym miejscu jest nie tak. Właśnie stworzyłem coś, czego nie da się skompilować, co nie ma prawa zadziałać przy próbie uruchomienia. Visual Studio zrozumiało to przede mną, a nawet podpowie nam w co najmniej dwóch miejscach jaka jest prawdopodobna przyczyna.

W dolnej części VS wyświetlane są błędy, którymi obarczony jest nasz program.

Chciałem wlać do baku naszej metody świeżo wydobytą ropę naftową co niechybnie skończyłoby się zniszczeniem silnika. Najpierw powinienem dokonać odpowiedniej rafinacji surowego stringa pobranego przez użytkownika i zamienić go na coś, co nasza metoda toleruje na wejściu, czyli na liczbę zmiennoprzecinkową – typ double.

Procedura zamiany stringów na typy liczbowe nazywa się parse’owaniem i posiada dwa bazowe warianty:

        string widthFromUser = Console.ReadLine();

        double width = double.Parse(widthFromUser);

        double saferWidth;
        bool converstionStatus = double.TryParse(widthFromUser, out saferWidth);

Wariant pierwszy, szybki to trzecia linijka z powyższego listingu. Przy jej pomocy wydajemy proste polecenie: zamień argument metody Parse na typ double.

Wariant drugi, czyli linijki piąta i szósta jest bardziej zachowawczy. Mówi on: spróbuj zamienić wartość tekstową na liczbę zmiennoprzecinkową.

Różnicę pomiędzy ‘zrób’ a ‘spróbuj’ odczujesz w sytuacji, gdy użytkownik sprawi Ci psikusa i nie poda liczby tylko losowy ciąg znaków, którego nie da się zamienić na liczbę. Wtedy zwykłe double.Parse wywoła wyjątek, czyli nieoczekiwany stan aplikacji, który w tym wariancie, zakończy nagle działanie programu. double.TryParse w kryzysowym momencie nie rzuci wyjątkiem, tylko elegancko zwróci wartość false jako dowód, że konwersja się nie powiodła, że coś poszło w tym miejscu nie tak. Zawsze zakładaj, że użytkownik skorzysta z Twojego narzędzia niezgodnie z zakładanym przeznaczeniem. Oczekuj nieoczekiwanego a Twoje życie będzie prostsze – Grzegorz Coehlo.

Obsługę wejścia użytkownika proponuję rozwiązać w poniższy sposób:

        string widthFromUser = Console.ReadLine();
        double width;
        if (!double.TryParse(widthFromUser, out width))
        {
            Console.WriteLine($"Wartość {widthFromUser} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            return;
        }

Skorzystałem z bezpiecznego TryParse, jednocześnie informując użytkownika dlaczego program zakończył pracę. Jeżeli wszystko poszłoby ok, to zawartość instrukcji if nie zostanie wykonana – zwróc uwagę na wykrzyknik przed wywołaniem metody double.TryParse. W normalnych warunkach, gdy konwersja się powiedzie TryParse zwróci wartość true, my chcemy obsłużyć przypadek przeciwny i to właśnie wykrzyknik niejako zamienia true na false i odwrotnie.

Teraz uważaj, założ na głowę kask budowlańca, bo za chwilę zwali nam się na głowę cały programistyczny świat grzmiąc ‘nigdy, przenigdy nie programuj metodą Kopijego-Pejsta‘. Ja założyłem, dlatego przedstawiam Ci kolejny listing:

        string widthFromUser = Console.ReadLine();
        double width;
        if (!double.TryParse(widthFromUser, out width))
        {
            Console.WriteLine($"Wartość {widthFromUser} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            return;
        }

        string heightFromUser = Console.ReadLine();
        double height;
        if (!double.TryParse(heightFromUser, out height))
        {
            Console.WriteLine($"Wartość {heightFromUser} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            return;
        }

Widzisz uderzające podpobieństwo przy drugiej próbie konwersji tekstu na liczbę? Wykorzystaliśmy dokładnie ten sam ‘algorytm’ zmiany, zwracamy ten sam komunikat, złamaliśmy dobrą zasadę DRY, czyli don’t repeat yourself. Kopiowanie fragmentów kodu w różne miejsca niemal zawsze mści się po pewnym czasie, gdy okazuje się, że musimy coś zmienić i zapominamy w ilu miejscach znajduje się dana funkcjonalność. Jak zrobić to porządniej? Wyekstrahować funkcjonalność do nowej metody, na przykład tak:

    public static double? GetDoubleFromUser(string inputValue)
    {
        double result;
        if (double.TryParse(inputValue, out result))
            return result;
        else
        {
            Console.WriteLine($"Wartość {inputValue} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            return null;
        }
    }

Zwróć uwagę na znak zapytania przy deklaracji zwracanego typu z naszej nowej metody. Zapis double? oznacza, że dana metoda może, ale nie musi zwrócić liczby. W przypadku niepowodzenia zwrócimy wartość null, którą traktuj jako nieistniejącą.

Po stworzeniu nowej metody GetDoubleFromUser możemy nasz program zapisać w poniższej postaci:

using System;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Podaj szerokość przekroju:");
        string widthFromUser = Console.ReadLine();
        double? width = GetDoubleFromUser(widthFromUser);
        if (!width.HasValue)
            return;

        Console.WriteLine("Podaj wysokość przekroju:");
        string heightFromUser = Console.ReadLine();
        double? height = GetDoubleFromUser(heightFromUser);
        if (!height.HasValue)
            return;

        double momentOfInertia = GetMomentOfInertia(width.Value, height.Value);

        string message = string.Format("Moment bezwładności przekroju wynosi {0:N2}", momentOfInertia);
        Console.WriteLine(message);

        Console.ReadLine();
    }

    public static double? GetDoubleFromUser(string inputValue)
    {
        double result;
        if (double.TryParse(inputValue, out result))
            return result;
        else
        {
            Console.WriteLine($"Wartość {inputValue} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            Console.ReadLine();
            return null;
        }
    }

    public static double GetMomentOfInertia(double b, double h)
    {
        double momentOfInertia = (b * Math.Pow(h, 3)) / 12;

        return momentOfInertia;
    }
}

Dodałem wskazówki/zachęty dla użytkownika przed właściwym odczytaniem wartości – zawsze to milej gdy program podpowie co teraz powinno się stać.

Nasza pierwsza aplikacja konsolowa działa, pobiera od użytkownika wejście, konwertuje wartości, wykonuje zadane przez nas działanie (wylicza moment bezwładności) i zwraca rezultat do konsoli. Nie jest źle, ale malkontenci powiedzą, że na kalkulatorze byłoby szybciej. W kolejnej części wzbogacimy naszą procedurę o kolejne elementy tak, żeby team Casio zaczął zmieniać zdanie 🙂

Series Navigation

Leave a Reply

Your email address will not be published. Required fields are marked *