Wątek przeniesiony 2016-10-06 19:52 z Newbie przez Ktos.

Pierwsza apka w C# do oceny i pytania laika

0

Cześć,
chcę napisać mała apkę w C# pod konsolą. Jest to tylko dla nauki, ten program nie musi robić nic ciekawego, po prostu chcę przećwiczyć sobie na przykładach język C#.
Napisałem takie dwie klasy jak poniżej.

Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MojaApka
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("\n\t  - MOJA APKA -  \n");
            Console.WriteLine("\n\tWybierz odpowiednią opcję z menu:\n");
            Console.WriteLine("\t1. Kalkulator\n");
            Console.WriteLine("\t2. Zarządzanie punktami\n");
            Console.WriteLine("\t3. Tablice\n");
            Console.WriteLine("\t4. Klasy abstrakcyjne\n");
            Console.WriteLine("\t0. Zakończenie programu\n");
            String odczytLinii = Console.ReadLine();
            int wyborOpcji = Int32.Parse(odczytLinii);

            switch(wyborOpcji)
            {
                case 1 :
                    Kalkulator Oblicz = new Kalkulator();
                    Console.Clear();
                    Console.Write("Podaj pierwsza liczbe: ");
                    String odczytLiczbyPierwszej = Console.ReadLine();
                    Oblicz.ustawWartoscLiczbyPierwszej(odczytLiczbyPierwszej);
                    Console.Write("\nPodaj druga liczbe: ");
                    String odczytLiczbyDrugiej = Console.ReadLine();
                    Oblicz.ustawWartoscLiczbyDrugiej(odczytLiczbyDrugiej);
                    Console.WriteLine("\nWybierz typ działania:");
                    Console.WriteLine("1. dodawanie");
                    Console.WriteLine("2. odejmowanie");
                    Console.WriteLine("3. mnożenie");
                    Console.WriteLine("4. dzielenie\n");
                    String odczytDzialania = Console.ReadLine();
                    int wyborDzialania = Int32.Parse(odczytDzialania);
                    if (wyborDzialania == 1)
                        Oblicz.dodajLiczby();
                    else if (wyborDzialania == 2)
                        Oblicz.odejmijLiczby();
                    else if (wyborDzialania == 3)
                        Oblicz.pomnozLiczby();
                    else if (wyborDzialania == 4)
                        Oblicz.podzielLiczby();       
                    break;
                case 2 :
                    Console.WriteLine("Wybrano opcję 2.");
                    break;
                case 3:
                    Console.WriteLine("Wybrano opcję 3.");
                    break;
                case 4:
                    Console.WriteLine("Wybrano opcję 4.");
                    break;
                case 0 :
                    Console.WriteLine("Naciśnij jakiś klawisz aby zakonczyc program...");
                    Console.ReadKey();
                    System.Diagnostics.Process.GetCurrentProcess().Kill();
                    break;
                default:
                    Console.WriteLine("Zły wybór!");
                    break;
            }
            Console.ReadKey();
        }
    }
}

Kalkulator.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MojaApka
{
    class Kalkulator
    {
        private int liczbaPierwsza = 0;
        private int liczbaDruga = 0;

        public void ustawWartoscLiczbyPierwszej(String odczytLiczbyPierwszej)
        {
            this.liczbaPierwsza = Int32.Parse(odczytLiczbyPierwszej);
        }
        public int pobierzWartoscLiczbyPierwszej()
        {
            return liczbaPierwsza;
        }
        public void ustawWartoscLiczbyDrugiej(String odczytLiczbyDrugiej)
        {
            this.liczbaDruga = Int32.Parse(odczytLiczbyDrugiej);
        }
        public int pobierzWartoscLiczbyDrugiej()
        {
            return liczbaDruga;
        }
        public void dodajLiczby()
        {
            Console.WriteLine("\nWynik dodawania to: {0}", liczbaPierwsza + liczbaDruga);
        }
        public void odejmijLiczby()
        {
            Console.WriteLine("\nWynik odejmowania to: {0}", liczbaPierwsza - liczbaDruga);
        }
        public void pomnozLiczby()
        {
            Console.WriteLine("\nWynik mnozenia to: {0}", liczbaPierwsza * liczbaDruga);
        }
        public void podzielLiczby()
        {
            Console.WriteLine("\nWynik dzielenia to: {0}", liczbaPierwsza / liczbaDruga); 
        }
    }
}

Moje pytania:

  1. Proszę o uwagi na temat błędów jakie zrobiłem w konstrukcji i logice programu (na razie działa tylko kalkulator - opcja 1).
  2. Jak uniknąć tego, że całą "obsługę" opcji 1. robię w switch-case? W C pisałem sobie oddzielną funkcję i od razu wychodziłem z switch-case i tam pisałem obsługę a tu czy należałoby wyjść czy to nie jest aż tak duży błąd?
  3. Jak to zrobić, żeby po wykonaniu działania program z powrotem wracał do "menu"? Czyli wychodził ze switch-case do początku programu tak jakby?

Proszę o wszystkie uwagi, przepiszę apke na nowo :)

2
  • Do obsługi liczby pierwszej i liczby drugiej w klasie Kalkulator możesz wykorzystać mechanizm właściwości (properties). To taki smaczek językowy, który jest dość podstawowy w C#, doczytaj o nim - ale służy właśnie do ustalania i pobrania wartości prywatnych pól klasy;
  • Funkcje dodaj, odejmij i tak dalej lepiej, aby zwracały wynik jako liczbę - obecnie zwracają tekst. Z tekstem nic ciekawego się nie zrobi, a liczbę można wykorzystać w kolejnych operacjach;
  • Pamiętaj, że dzielenie int przez int daje int;
  • Uwaga na przyszłość: publiczne metody nazywa się z wielkiej litery, np. DodajLiczby(), a prywatne - z małej, a z małej litery prywatne pola, zmienne albo parametry. Typ string zazwyczaj też zapisuje się w wersji z małej litery.
  1. Możesz zrobić kolejną metodę w klasie Program, która odpowiada za całą logikę wczytywania liczb do kalkulatora i tak dalej. Zasadniczo lepiej, funkcje im krótsze tym zazwyczaj lepsze.
  2. Użyj pętli, np. while.
int wyborOpcji = 1;
while (wyborOpcji != 0)
{
    String odczytLinii = Console.ReadLine();
    wyborOpcji = Int32.Parse(odczytLinii);

    switch(wyborOpcji)
    {
        case 1 :
            // tutaj się dzieją rzeczy
        case 0 :
            Console.WriteLine("Naciśnij jakiś klawisz aby zakonczyc program...");            
            break;
        default:
            Console.WriteLine("Zły wybór!");
            break;
    }
}

Po zakończeniu obsługi poszczególnych przypadków program "wróci" na górę, sprawdzi warunek i jeżeli nie było podane 0 to zapyta jeszcze raz. A jeżeli 0 - to pętla się skończy i wyjdzie poza nią, czyli do końca programu.

1

Nazwy metod w C# zawsze piszemy z wielkiej litery, wszystko jedno czy są to metody publiczne, chronione czy prywatne. Stosowanie małych i wielkich liter dotyczy pól oraz własności.
Poza tym jeżeli już stosujesz int.Parse() to obsłuż również wyjątek, który na pewno poleci jeżeli user wpisze NaN z klawiatury. W ogóle dla tak częstych przypadków warto zamienić Parse na TryParse i obsłużyć błąd zwykłym if'em zamiast od razu rzucać wyjątek.

0

Wooow dzięki za tak merytoryczne odpowiedzi i poświęcony czas!!! :)
Poprawiłem według Waszych uwag i wygląda to tak:

Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MojaApka
{
    class Program
    {
        static void Main()
        {
            int wyborOpcji = 1;
            while (wyborOpcji != 0)
            {
                Console.WriteLine("\n\t  - MOJA APKA -  \n");
                Console.WriteLine("\n\tWybierz odpowiednią opcję z menu:\n");
                Console.WriteLine("\t1. Kalkulator\n");
                Console.WriteLine("\t2. Zarządzanie punktami na osi 3D\n");
                Console.WriteLine("\t3. Tablice\n");
                Console.WriteLine("\t4. Klasy abstrakcyjne\n");
                Console.WriteLine("\t0. Zakończenie programu\n");
                string odczytLinii = Console.ReadLine();
                try
                {
                    wyborOpcji = Int32.Parse(odczytLinii);
                }
                catch(FormatException wyjatekWyborOpcjiMenu)
                {
                    Console.WriteLine("\nNie wpisano liczby!!!\n");
                    Console.Write("Komunikat systemowy: ");
                    Console.WriteLine(wyjatekWyborOpcjiMenu.Message);
                    KomunikatOBrakuObslugi();
                }

                switch (wyborOpcji)
                {
                    case 1:
                        UruchomKalkulator();
                        break;
                    case 2:
                        UruchomZarzadzaniePunktamiNaOsi3D();
                        break;
                    case 3:
                        UruchomProgramTablice();
                        break;
                    case 4:
                        UruchomKlasyAbstrakcyjne();
                        break;
                    case 0:
                        Console.WriteLine("Naciśnij jakiś klawisz aby zakonczyc program...");
                        Console.ReadKey();
                        System.Diagnostics.Process.GetCurrentProcess().Kill();
                        break;
                    default:
                        Console.Beep();
                        Console.WriteLine("Zły wybór!");
                        break;
                }
            }
                Console.ReadKey();
        }
        public static void UruchomKalkulator()
        {
            Kalkulator Oblicz = new Kalkulator();
            Console.Clear();
           
            Console.Write("Podaj pierwsza liczbe: ");
            string odczytLiczbyPierwszej = Console.ReadLine();
            Oblicz.liczbaPierwsza = Int32.Parse(odczytLiczbyPierwszej);
            
            Console.Write("\nPodaj druga liczbe: ");
            string odczytLiczbyDrugiej = Console.ReadLine();
            Oblicz.liczbaDruga = Int32.Parse(odczytLiczbyDrugiej);
                        
            Console.WriteLine("\nWybierz typ działania:");
            Console.WriteLine("1. dodawanie");
            Console.WriteLine("2. odejmowanie");
            Console.WriteLine("3. mnożenie");
            Console.WriteLine("4. dzielenie\n");
            string odczytDzialania = Console.ReadLine();
            int wyborDzialania = Int32.Parse(odczytDzialania);
            if (wyborDzialania == 1)
                Console.WriteLine("\nWynik dodawania to: {0}", Oblicz.DodajLiczby());
            else if (wyborDzialania == 2)
                Console.WriteLine("\nWynik odejmowania to: {0}", Oblicz.OdejmijLiczby());
            else if (wyborDzialania == 3)
                Console.WriteLine("\nWynik mnozenia to: {0}", Oblicz.PomnozLiczby());
            else if (wyborDzialania == 4)
                Console.WriteLine("\nWynik dzielenia to: {0}", Oblicz.PodzielLiczby());   
        }
        public static void UruchomZarzadzaniePunktamiNaOsi3D()
        {
            Console.WriteLine("Wybrano opcję 2. - zarządzanie punktami na osi 3D");
            KomunikatOBrakuObslugi();
        }
        public static void UruchomProgramTablice()
        {
            Console.WriteLine("Wybrano opcję 3. - program z obsługą tablic");
            KomunikatOBrakuObslugi();
        }
        public static void UruchomKlasyAbstrakcyjne()
        {
            Console.WriteLine("Wybrano opcję 4. - program z klasami abstrakcyjnymi");
            KomunikatOBrakuObslugi();
        }
        public static void KomunikatOBrakuObslugi()
        {
            Console.WriteLine("Brak tej funkcji w programie!");
            Console.WriteLine("Naciśnij jakiś klawisz aby zakonczyc program...");
            Console.ReadKey();
            System.Diagnostics.Process.GetCurrentProcess().Kill();
        }
    }
}

Kalkulator.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MojaApka
{
    class Kalkulator
    {
        private int _liczbaPierwsza = 0;
        private int _liczbaDruga = 0;

        public int liczbaPierwsza
        {
            get
            {
                return _liczbaPierwsza;
            }
            set
            {
                _liczbaPierwsza = value;
            }
        }
        public int liczbaDruga
        {
            get
            {
                return _liczbaDruga;
            }
            set
            {
                _liczbaDruga = value;
            }
        }
        public int DodajLiczby()
        {
            return _liczbaPierwsza + _liczbaDruga;
        }
        public int OdejmijLiczby()
        {
            return _liczbaPierwsza - _liczbaDruga;
        }
        public int PomnozLiczby()
        {
            return _liczbaPierwsza * _liczbaDruga;
        }
        public int PodzielLiczby()
        {
            int wynikDzielenia = 1;
            try
            {
                wynikDzielenia = _liczbaPierwsza / _liczbaDruga;
                //return _liczbaPierwsza / _liczbaDruga; 
            }
            catch(DivideByZeroException wyjatekDzieleniePrzezZero)
            {
                Console.WriteLine("Dzielenie przez ZERO jest zabronione!");
                Console.Write("Komunikat systemowy: ");
                Console.WriteLine(wyjatekDzieleniePrzezZero.Message);
                Console.WriteLine("Naciśnij jakiś klawisz aby zakonczyc program...");
                Console.ReadKey();
                System.Diagnostics.Process.GetCurrentProcess().Kill();
            }
            return wynikDzielenia;
        }
    }
}

Moje pytania:
0. Proszę o ogólną ocenę pokazanych kodów i uwagi co jest do kitu :)

  1. Czemu String z dużej litery świecił sie na zielono a string z małej świeci na niebiesko? Przecież jeden i drugi jest chyba typu obiektowego i można wykonywac na nim operacje jak na obiektach? Program po tej zmianie działa tak samo.
  2. Czy w pliku Program.cs metoda Main ma być na górze a inne metody pod nią czy na samej górze inne metody a Main na samym dole? W C często Main był na samym dole...
  3. Pola w klasie (postaram się aby wszystkie zawsze były private) nazwałem od znaku podkreślenia "_" - czy to dobra praktyka? Wiem to z książki Marcina Lisa Praktyczny kurs C#.
  4. Do zmian nazw metod i pół używam Refactora w VS - czy to jest ok?
  5. Nie mogłem zrobić TryParse bo ja najpierw pobieram liczby a dopiero potem decyduję jakie działanie na nich wykonam.
    Gdybym chciał skorzystać z TryParse to musiałbym najpierw zdecydowac się na typ działania i dopiero później pod tym
    kątem sprawdzać poprawnośc liczb za pomocą TryParse... Czy ja jednak źle myślę?
  6. Czemu jak wpiszę liczby do dzielenia 8 i 0 to wywala wyjątek a jak wpiszę 0 i 8 to działa normalnie?
  7. Czemu Console.Beep(); nie działa? Nie słyszę głośnika...
  8. Jak zrobić to, żeby przy wybieraniu opcji z menu początkowego program reagował na wpisanie np. literek albo czegolokwiek innego niż integer? Bo jeżeli wprowadzę jakiegoś głupiego int'a to program przechodzi do default w switchu i jest ok. Natomiast jak wprowadzę literkę to wywala wyjątek FormatException. Obsłużę ten wyjątek ale wydaje mi się, że powinien być jakiś bardziej szczegółowy wyjątek dziedziczący z FormatException a nie mogłem go odnaleźć...

Pozdrawiam! :)

0
  1. Bo String to typ .NET, a string to alias z C#. Wszystkie typy są zawsze obiektami, nie ma to znaczenia, ale konwencja mówi, aby używać raczej aliasów,
  2. Nie ma znaczenia, nawet nie wiem czy jest konwencja na to,
  3. Kojarzę taką praktykę, sam nie stosuję,
  4. Tak, jak najbardziej - refactor to jedna z najbardziej przydatnych opcji,
  5. Bo 0 da się podzielić na 8, ale 8 na 0 się nie da :-)

[dodane]
Zamiast:

private int _liczbaPierwsza = 0;        

public int liczbaPierwsza
{
    get
    {
        return _liczbaPierwsza;
    }
    set
    {
        _liczbaPierwsza = value;
    }
}

Możesz zrobić:

public int LiczbaPierwsza { get; set; }

W momencie, kiedy getter i setter nic nadzwyczajnego nie robią, to spokojnie można użyć ich automatycznie tworzonych wersji.
Poza tym przechwytywanie wyjątku i wyłączanie aplikacji wtedy nie pasuje mi osobiście.

Co do TryParse: nie ma znaczenia czy używasz w takiej opcji, TryParse po prostu nie będzie rzucało wyjątku, a Parse tak.

0

Dzięki za info :)

Właśnie wyjaśniłeś mi trochę sposób stosowania getterów i setterów bo teraz piszę taką klasę jak poniżej i ona ma 5 pól:

class Klient
    {
        // 5 skladowych klasy Klient charakteryzuje klienta.
        public string _nazwaFirmy, _adres;
        public int _nip, _iloscKierowcow, _iloscAktualnychPism;

        public Klient(string _nazwaFirmy, string _adres, int _nip, int _iloscKierowcow, int _iloscAktualnychPism)
        {
            this._nazwaFirmy = _nazwaFirmy;
            this._adres = _adres;
            this._nip = _nip;
            this._iloscKierowcow = _iloscKierowcow;
            this._iloscAktualnychPism = _iloscAktualnychPism;
        }
    }

I zastanawiałem się, czy dla każdego z tych pól trzeba napisać getter i setter?

I czy tej techniki też się używa jeśli klasa ma 200 pól? :)

I co zrobić przy takiej klasie np. 200 pól = jak zainicjować te pola?
Jednym konstruktorem??

Pozdrawiam!

1

Kilka kwestii z pogranicza kowencji a osobistych preferencji:

  1. Co do podkreślania pól klasy- jak najbardziej tak. Jest to również często stosowana praktyka w kodzie framework'a .Net.
  2. Słowo kluczowe this jest raczej zbędne. Poza tym dosyć brzydko wygląda w zestawieniu z podkreślnikiem. Już sam podkreślnik daje wyraźnie do zrozumienia że mamy do czynienia z polem klasy, jaki cel ma więc spełniać this? (Patrz punkt poniżej)
  3. Parametry konstruktora to zmienne lokalne a więc nie podkreślasz ich.
  4. Tak jak napisał @Ktos możesz użyć automatycznych właściwości jeśli nie potrzebujesz kontroli nad tym jaka wartość jest ustawiana/zwracana.
  5. 200 pól w klasie świadczy raczej o jej bardzo złym zaprojektowaniu, takiego molocha należało by rozbić na poszczególne klasy jeśli nic nie stoi na przeszkodzie.
  6. Co do inicjowania pól za pomocą konstruktora- moim zdaniem konstruktor powinien zawierać parametry dla kluczowych wartości, resztę można przypisać odwołując się do właściwości, np:
 
Person person = new Person(202);//202 to ID obiektu
person.Name = "John";

Poza tym pola warto inicjować w deklaracji do ich domyślnych (lub innych) wartości, np:

private Address _address = null;
private int _age = 0;

Ponieważ
a) Unikniesz odwołania do niezainicjowanego pola jeśli nie zostało zainicjowane w konstruktorze
b) Dajesz jasno do zrozumienia innym programistom patrzącym na Twój kod jakie były Twoje intencje, szczególnie jeśli wartość domyślna jest inna niż wartość danego typu (np. 0 dla typu int)

0
  1. Kod z pętli while w Main można spokojnie rozbić na 3 funkcje: wyświetlenie menu, pobranie wyboru użytkownika, przekierowanie sterowania na podstawie tego wyboru. Analogicznie w UruchomKalkulator.

  2. Nazwy zmiennych lokalnych zaczyna się małą literą, a więc nie Oblicz lecz oblicz.

  3. Nazwy właściwości zaczyna się wielką literą, a więc LiczbaDruga, a nie liczbaDruga.

  4. W wielu miejscach pobierasz od użytkownika tekst, który zamieniasz na liczbę. Zamiast kopiować te same linie kodu wielokrotnie, mógłbyś utworzyć funkcję, która przyjmuje w argumencie treść pytania, a zwraca liczbę.

  5. Tworzysz sobie obiekt klasy Kalkulator, a następnie ustawiasz w jego polach wartości, na których ma operować. Czemu tak? Czemu po prostu nie przekazać tych liczb do odpowiedniej metody, która ma wykonać działanie?

  6. Klasa Kalkulator powinna się zajmować wyłącznie obliczeniami. Wywal z niej wszystkie Console.WriteLine.

  7. Oblicz.liczbaPierwsza = Int32.Parse(odczytLiczbyPierwszej); - to bzdura, nie masz tu żadnych liczb pierwszych, co najwyżej pierwszą liczbę.

  1. Czy w pliku Program.cs metoda Main ma być na górze a inne metody pod nią czy na samej górze inne metody a Main na samym dole? W C często Main był na samym dol

Bo C jest stare i wymaga, aby każda funkcja była zadeklarowana przed użyciem. C# jest "mądrzejszy" pod tym względem i nie ma takiego wymagania.
Generalnie zasady dobrego pisania kodu mówią o tym, że kod się ma dać czytać jak książkę, czyli najpierw czytasz ważniejszą funkcję, która korzysta z funkcji pomocniczych znajdujących się za nią.

  1. Pola w klasie (postaram się aby wszystkie zawsze były private) nazwałem od znaku podkreślenia "_" - czy to dobra praktyka? Wiem to z książki Marcina Lisa Praktyczny kurs C#.

No to taki styl lat 80tych - duże fiaty, ocet na półkach i Czarnobyl. Ale co kto lubi. ;)

  1. Nie mogłem zrobić TryParse bo ja najpierw pobieram liczby a dopiero potem decyduję jakie działanie na nich wykonam.
    Gdybym chciał skorzystać z TryParse to musiałbym najpierw zdecydowac się na typ działania i dopiero później pod tym
    kątem sprawdzać poprawnośc liczb za pomocą TryParse... Czy ja jednak źle myślę?

Kolejność decyzji nie ma znaczenia. Po prostu zadeklaruj zmienna odpowiedniego typu, a potem napisz kod w stylu:

if(int.TryParse(out zmienna, string s))
{
  // ok
}
{
  // błąd, wprowadź liczbę jeszcze raz
}
  1. Czemu Console.Beep(); nie działa? Nie słyszę głośnika...

Jaki system operacyjny? Pracujesz na laptopie czy stacjonarce?

  1. Jak zrobić to, żeby przy wybieraniu opcji z menu początkowego program reagował na wpisanie np. literek albo czegolokwiek innego niż integer? Bo jeżeli wprowadzę jakiegoś głupiego int'a to program przechodzi do default w switchu i jest ok. Natomiast jak wprowadzę literkę to wywala wyjątek FormatException. Obsłużę ten wyjątek ale wydaje mi się, że powinien być jakiś bardziej szczegółowy wyjątek dziedziczący z FormatException a nie mogłem go odnaleźć...

Użyć int.TryParse. Nie ma wyjątku, którego szukasz. Po co miałby być?

I zastanawiałem się, **czy dla każdego z tych pól trzeba napisać getter i setter?

Getter piszesz jak chcesz pobierać wartość, setter jak chcesz ustawiać.

I czy tej techniki też się używa jeśli klasa ma 200 pól?

Jeśli klasa ma 200 pól, to:
a) Albo jest źle zaprojektowana i robi to, co powinno robić 20 klas.
b) Jest jakimś DTO (obiektem służącym do wymiany danych między różnymi komponentami/systemami).

I co zrobić przy takiej klasie np. 200 pól = jak zainicjować te pola?
Jednym konstruktorem??

W przypadku normalnych klas nie pisze się setterów, tylko ustawia wszystko w konstruktorze. W przypadku DTO nie pisze się konstruktorów tylko ustawia wszytko w setterach. W tej sytuacji można użyć inicjalizacji obiektu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1