Pomoc z kodem - C# podstawy


#1

Witam. Chciał bym prosić o pomoc gdyż dopiero zaczynam naukę z programowanie w C# i mało ogarniam. Chciał bym się spytać dla czego mi nie wykonuje polecenie Console.WriteLine w public void Wypisz()? Chciał bym tylko zaznaczyć że robie to jako zadanie i dane ucznia muszą zostać wywołane w publicznej metodzie Void a nie inaczej.
Za wszelką pomoc dziękuje

Kod:
using System;

public class Uczen
{
    public string Imie { set; get; }
    public string Nazwisko { set; get; }
    public long Pesel { set; get; }
    public Uczen() { }
    public Uczen(string imie, string nazwisko, long pesel)
    {
        (Imie, Nazwisko, Pesel) = (imie, nazwisko, pesel);
    }
    Uczen u1 = new Uczen("Adam", "Kowalski", 05285295351);
    public void Wypisz()
    {
        Console.WriteLine(Imie ,"Nazwisko: " ,Nazwisko ,"PESEL: " ,Pesel);
    }
}


public class HelloWorld
{
    static void Main()
    {
        Console.WriteLine("Koniec programu");
    }
}

#2

Wszystko to co stworzyłeś w klasie Uczeń nie wpływa na działanie programu w żaden sposób.
Twój program rozpoczyna się od wykonywania metody Main(), w której wykonujesz tylko:
Console.WriteLine("Koniec programu");

Specjalistą nie jestem i nie wytłumaczę tego dobrze, ale przenieś linijkę odpowiedzialną za tworzenie obiektu do funkcji Main.
Uczen u1 = new Uczen("Adam", "Kowalski", 05285295351);
a następnie dodaj wywołanie metody Wypisz dla tego obiektu.
u1.Wypisz()

Rozważ jeszcze zamianę
Console.WriteLine(Imie ,"Nazwisko: " ,Nazwisko ,"PESEL: " ,Pesel);
na
Console.WriteLine(Imie + " Nazwisko: " + Nazwisko + "PESEL: " + Pesel);


#3

JW. Tak przy okazji jaki zostawisz tworzenie ucznia w klasie Uczen to prawdopodobnie Ci poleci jakiś wyjątek w stylu stackoverflow bo wpadniesz w rekurencyjną pętlę tworzenia nowej instancji (ciekawe w sumie czy tak to się zachowa w C#).


#4

Słuszna uwaga. Nie testowałem kodu, ale na pewno taki wyjątek się pojawi.


#5

To tak:

  1. Błąd jaki popełniłeś wskazali Ci koledzy i rzeczywiście dostałbyś stackoverflow jako błąd ze względu na niekończącą się rekurencję w ramach tworzenia obiektu. Rekurencja to wywołanie fragmentu oprogramowania przez samego siebie. Co prawda działa to nieco inaczej ale rekurencją w życiu jest drzemka w telefonie. Po uruchomieniu moze wywlac kolejna drzemke, ktora zadziała identycznie jak poprzednia. Gdybyś cały czas klikał drzemkę mógłbyś tak trwać do końca świata.
  2. C# ma w swoich zasadach jeden plik jedna klasa. Plik nazywa sie tak jak klasa. Wyjatkiem sa klasy prywatne i chronione. Tym samym klase Uczen powinienes miec w Uczen.cs. Korzystanie nie roznilo by sie od tego co zastosowales.
  3. Pola i wlasciwosci umieszczamy u góry klasy, tutaj odnosze sie do tego blednego stworzenia Uczen w srodku klasy, ale warto o tym pamietac. W miare mozliwosci tez powinny byc one alfabetycznie.
  4. Stosujesz tuple (…,…) = (…,…) i fajnie bo to zajebista rzecz w C#. Ale mozesz tez robic skrócony zapis samej inicjalizacji. Zapis:
    public Uczen(…,…)
    => (…,…) = (…,…);
    Taki zapis wystarczy i nie trzeba robic klamr.
  5. Unikaj zapisu Console.WriteLine z przecinkami, jest to bardzo wolne działanie. Również wolnym choc szybszym od przecinka jest +. Nowoczesny i zalecany zapis to StringBuilder lub też pewnie Ci bardziej przypadnie do gustu, bo krótszy: użycie $ przed tekstem:
    Np:
    Console.WriteLine($”Imie: {Imie}, Nazwisko: {Nazwisko}, Pesel: {Pesel}”);

Oczywiscie w takim zapisie { i } oznaczaja ze w tym miejscu wyliczasz wartosc lub podajesz zmienna ktora da wartosc. W Twoim wypadku wlasciwosc. Jezeli chcesz miec klamrw jednak w tekscie jedna lub druga to musisz zastosowac zapis {{, czyli dwie klamry. Oczywiscie wyzej przecinek to element tekstu.
6) Twoje wlasciwosci powinny byc niemozliwe do ustawienia z zewnatrz. Obiekt uczeń nie zmienia imienia nazwiska ani pesel. Oczywiscie w zyciu moze sie tak zdarzyc, niemniej pesel jest sto procent staly. Zgodnie z zasadami SOLID powinienes miec tak zapisane właściwości:
public string Imie { get; protected set;}
public string Nazwisko { get; protected set;}
public long Pesel { get;}

A chcac dac mozliwosc zmiany imienia i nazwiska stworzyc odpowiednia metode. Protected oznacza ze mozna zmieniac tylko w tej klasie ta wartosc jak i w klasach dziedziczacych. Jezeli chodzi o sam Pesel nie ma on set co oznacza ze musi byc ustawiony w chwili tworzenia obiektu w konstruktorze zazwyczaj, ale moze tez byc ustawiony na stale poprzez = wartosc, za deklaracja. Inna opcja zapisu jest ustawienie tzw readonly czyli ustawienia ze dana wartosc raz ustawiona nie bedzie zmieniana.
7) Zgodnie z zasadami programowania obiektowego powinienes unikac tworzenia smieciowych obiektow. Smieciowym obiektem jest obiekt klasy Uczen bez ustawionych danych. Jasne chciales sobie ulatwic przez dodawanie z zewnatrz, lecz klasa nie jest zbiorem danych lecz funkcjonalnym obiektem. Tym samym nie powinen sie pojawic pusty konstruktor.
8) Zobacz tez ze sam typem long doprowadziles do blednego wypisania pesel testowego, ktory zaczyna sie od zera. A typy liczbowe usuwaja zera wiodace, czyli zera z przodu.

Choc moze Ci sie wydawac ze to wiele kłopotów w tak krótkim kodzie, to wiekszosc programistow nie zauwazy tego co wyzej napisalem. Malo sie przywiazuje sprawy do czystego kodu i zgodnego z wszelkimi zasadami. To co wyzej tez jest radami od senior programisty od Ciebie jako juniora wymaga sie przede wszystkim bys pisał samodzielnie i poprawnie kod, a jak aplikacja w dzialaniu bedzie szybka to masz wszystko co potrzeba na start jako programista.

Powodzenia, skoro zaczynasz od tuple to znaczy ze dosc swiezy material masz do nauki, jezeli masz trudnosci z nauka to mozesz skorzystac np z mojej ksiazki. Znajdziesz ja tutaj: https://appg.pl/MyslJakProgramista/
Akurat darmowy fragment moze Ci pomóc w początkach jakie tutaj masz.


#6

Czy protected nie jest czasami dla klas dziedziczących jeśli robimy klasę abstrakcyjną.

Private jest natomiast tylko dla danej klasy i nie jest dostępne na zewnątrz nawet dla klas dziedziczących.


#7

Zasięgi czy jak ktoś woli modyfikatory dostępu są ustalane wraz z deklaracją danego elementu klasy lub klasy.
Protected używasz w chwili w której potrzebujesz by z zewnątrz nie można było mieć dostępu do danego elementu. W przypadku właściwości możesz mieć dwa modyfikatory dostępu i jeden stawiasz przed właściwością a drugi przy get lub set. Wtedy ten get/set korzysta z tego modyfikatora dostepu a drugi element właściwości z tego przed deklaracją właściwości.

Protected możesz użyć jeżeli potrzebujesz na przykład stworzyć klasę bazową Zwierze i w niej mieć na przykład pole Waga. Każde zwierze ma wagę i może być ona potrzebna na przykład do obliczenia siły mięśni, czy też ile potrzebuje karmy. W takim wypadku używasz protected bo chcesz by pozostałe klasy, które odziedziczą miały dostęp do pola Waga. Jeżeli użyłbyś private i np stworzyl klasę Pies, która dziedziczy po Zwierze to wtedy nie mógłbyś posługiwać się tym polem. To samo oczywiscie dotyczy kazdego dowolnego elementu klasy.

Private jest ograniczajace do danej klasy i z zewnatrz obiektu nie ma sie do niej dostepu, podobnie w przypadku protected. Różnica jest taka ze wlaśnie dziedzicząc we wnetrzu klasy mozna uzywac tego elementu.

W przypadku C# modyfikatory dostepu są bezstratne lub jak ktos woli stałe. To znaczy protected bedzie protected nawet po dziesieciu dziedziczeniach. Wiem ze sa jezyki gdzie protected zmienia sie w private po dziedziczeniu i jest to chyba Java i C++. Ale sa tez jezyki w ktorych protected nie ma lub jego odpowiednika.

Jezeli chodzi o klasy abstrakcyjne to tak, private praktycznie tam nie wystepuje, a protected jest na wszystko co ma nie byc dostepne do zmiany w wewnatrz gotowego obiektu. Ja tu polecam sobie wyobrazic protected i private jako taka obudowe na sprzet elektroniczny, np telewizor. Telewizor sobie dziala a my mamy dostep tylko do jego pilota i przyciskow. Jakby sie moglo skonczyc korzystaniw z telewizora bez obudowy mozna sobie wyobrazic i jest to wlasnie szastanie public i internal.

Nie jest bledem uzywanie protected zamiast private dla kazdej klasy jaka sie tworzy chyba ze jest ona zapieczetowana ( nie mozna po niej dziedziczyc ) lub jest klasa statyczna. Kazda klasa moze byc odziedziczona by ja rozwijac a dzieki polimorfizmowi bedzie mozna korzystac z tej rozbudowanej formy w juz istniejacych klasach. Choc tez tu solid chce by korzystac z klas abstrakcyjnych jak i interfejsow, ale zycie pokazuje inaczej :wink: To ze np Ty stwierdzasz ze klasa Uczen jest ta ostateczna nie oznacza ze w ramach calego projektu i jego rozwoju taka bedzie. Byc moze klasa Uczen bedzie potrzebowac Id ucznia i wtedy modyfikacja klasy samej Uczen jest bledem. Nalezy odziedziczyc po klasie Uczen np jako klasa UczenPlus i tam dodac. I teraz jakbys np chcial Id wyliczac np na podstawie PESEL a ten mial niedostepny bo zastosowano private to jestes zmuszony do modyfikacji klasy Uczen. Klasy z kolei powinny byc tak pisane by byly gotowe na rozbudowe, czyli dziedziczenie. Private w C# powinno być świętem dla pól i właściwości. Z kolei kazde metody powinny byc obarczane virtual jezeli masz tylko przypuszczenie ze moze ulec ona potrzebie zmian w ramach rozbudowy.

Ogólnie rzecz biorąc private jest w obrebie tylko danej klasy od jej pierwsze klamry do ostatniej. Bardzo nieintuicyjna rzecza dla przechodzacych z innych jezyków w których jest to realizowane inaczej jest to ze modyfikatory dostepu w C# sa obszarowe a nie per obiekt. Jezeli chcesz np stworzyc metode, ktora przyjmie jako parametr obiekt tej samej klasy co aktualnie pisana to mozesz miec dostep do elementow prywatnych takiego obiektu mimo, ze w innej klasie nie masz takiego dostepu. Np do konstruktora by stworzyc kopie jakiegos obiektu to wtedy nie potrzebujesz public czy internal, bo masz dostep do elementow private i protected.

C# ma tez nowe dwa modyfikatory dostepu, które sie zmieniaja i sa to protected private oraz protected internal. Kolejnosc tych polaczen jest obojetna. Roznia sie oni pomiedzy projektami w solucji.

Chyba na tym skończę bo temat modyfikatorów dostepu jest dość obszerny.


#8

https://dotnetfiddle.net/mqId1N

Są 3 głupie błędy:

  • jakiś losowy kawałek kodu w klasie a nie w mainie
  • w mainie wypadałoby coś wywołać
  • Console.Writeline(a,b,c,d,e,…) robi bardzo co innego niż ten kod próbował robić
    wypisuje pierwszy napis a reszty używa do wstawiania parametrów do format stringa

#9

Jeszcze sprostowania do uwag powyżej:

  • 1 plik 1 klasa - to jest dobra praktyka ale nie jest to wymagane
  • pola an górze albo alfabetycznie - styl nie jest taki oczywisty i język nie wymusza, ale warto mieć poorządek
  • tuple w konstruktorze, to jest IMO zupełnie niepotrzebny bajer, polecam normalne A = a;
  • WriteLine - to nie jest niewydajne tylko nie działa, ale sam się zdziwiłem, polecam nie korzystać z magii tylko normalnie pisać to rzeczy działają. String builder to overkill dla 4 pól ale jak się pracuje z większą ilością tekstu warto.
  • SOLID jest dużo bardziej złożony i na poziomie takiego kodu to enkapsulacja i zasady projektowania są zupełnie nie istotne, to ma znaczenie jak kodu jest więcej i trzeba z nim pracować
  • pusty konstruktor - też polecam wywalić, jest nieużywany i nie powinno go być, czasem trzeba dodać do serializacji itp jak biblioteka wymaga
  • typy liczbowe nie tyle usuwają zera co zapisują wartość liczby, ale wniosek podobny - PESEL powinien być napisem żeby gdzieś czegoś nie ucięło itp, od biedy można sformatować przy wypisywaniu żeby dodawał zera ale to dość kuriozalne
  • clean code fajna sprawa ale nie jak nie umiesz napisać prostej klasy, to ma znaczenie jak już masz więcej kodu i coś umiesz napisać
  • modyfikatory do stępu są kompletnie nieistotne na kodzie tej wielkości, tylko ważne żeby kalsa i konstruktor były publiczne i są, jeśli chodzi o styl to teraz kompozycja jest zalecana zamiast dziedziczenia, bo dziedziczenie ma dużo problemów, nie tylko nieszczęsne protected które jest bardzo upierdliwe, za to polecam interfejsy żeby łatwo podmieniać klasy na inne