sobota, czerwiec 04, 2011

3 problematyczne zagadnienia dla początkującego dotnetomaniaka

Ostatnio, przez ponad pół roku, wraz z Wojtkiem Poniatowskim, Mirkiem Pragłowskim, Tomkiem Wiśniewskim prowadziliśmy kurs C# na portalu VirtualStudy. Kurs przeznaczony był dla osób początkujących ale dzięki temu miałem możliwość zobaczenia jakie elementy .NET i C# sprawiają najwięcej trudności osobom, które dopiero zaczynają swoją przygodę z tą technologią. Jeśli masz choć trochę doświadczenia z .NET to zapewne nie znajdziesz tu zbyt wielu przydatnych informacji. Tak czy inaczej zapraszam do czytania:

jawny typ vs. var vs dynamic

Po ilości pytań odnośnie tego zagadnienia wnioskuję, że jest to numer jeden “problemów” z jakim muszą sobie poradzić osoby dopiero zaczynające w .NET. Podawać typ? Użyć var? A tak w ogóle czym to się różni od tego dynamic? Zacznijmy zatem od początku. Dawno dawno temu w czasach .NET 2.0 i wcześniejszych aby zadeklarować i utworzyć zmienną należało posłużyć się następującą konstrukcją

Importer importer = new Importer();

Czyli nazwa typu, potem nazwa zmiennej i przypisanie wartości. Widać, że jest to dość spora duplikacja. Skoro po prawej stronie po słowie kluczowym new mamy już nazwę typu po co podawać ją jeszcze z lewej strony? Wychodząc z tego założenia twórcy .NET w jego wersji 3.5 dołożyli słowo kluczowe var. Dzięki temu od frameworka w tej wersji możemy napisać

var importer = new Importer();

i ma to takie samo znaczenie jak powyższy zapis. Słowo kluczowe var mówi kompilatorowi – jestem zbyt leniwy, aby podać jawnie typ, ale ty drogi kompilatorze domyśl się jaki powinien on być i wpisz go za mnie zanim przeprowadzisz kompilację. Dzięki temu nasz program jest nadal silnie typowany.

Niestety dzięki temu można tworzyć kod, który czyta się dość ciężko a mianowicie taki:

var user = GetLoggedUser();

Czemu jest on taki zły? Dlatego, że bez sprawdzenia w nagłówku metody GetLoggerUser, lub bez najechania kursorem myszy na słówko var nie zobaczymy, jakiego typu jest user. Skoro var ma takie minusy czemu został w ogóle wprowadzony do języka? Bez niego ciężko byłoby działać z użyciem LINQu. W prostych przypadkach moglibyśmy typ podawać jawnie, ale pewnie niewielu osobom będzie się chciało domyślać się jaki będzie typ wyrażenia

Enumerable.Range(0, 1000).Where(i => i%5 == 0 || i%3 == 0).GroupBy(x => x%3)

Dzięki var, możemy to wyrażenie przypisać do zmiennej, którą zadeklarujemy z tym słowem kluczowym i kompilator sam domyśli się typu. My nie tracimy czasu na określanie typu (który byłby dość długi) a nadal mamy pełne silne typowanie.

Drugim przypadkiem, także w LINQu, są typy anonimowe. Bez var, nie moglibyśmy napisać

var anonimowy = new {Title = "Anonimowi", Age = 99};

Skoro wiemy już jaka jest różnica pomiędzy jawnym podaniem typu a var to czymże jest dynamic? To słowo kluczowe jest nowością w .NET 4 i oznacza, że nie wiemy jaki jest typ zmiennej. Nie wiemy jakie udostępnia ona metody i właściwości. Dopiero w runtime może się okazać czy dana metoda istnieje na tym obiekcie czy nie. Dzięki dynamic możemy wywołać dowolną metodę i kompilator zbuduje nasz program bez przeszkód. Dopiero jednak w runtime okaże się czy dana operacja powiedzie się i czy podana metoda istnieje na obiekcie. Zobaczmy na przykładzie, czym różni się dynamic od var i jawnego typu.

Zdefiniujmy sobie 3 zmienne

string zmienna = "jawny typ";

var zmiennaVar = "var";

dynamic zmiennaDynamic = "dynamic";

Zobaczmy na co pozwoli nam kompilator. W przypadku zmienna oraz zmiennaVar mamy dostęp tylko i wyłącznie do metod zdefiniowanych na typie string (+ wszystkie extension metody). Jeśli chodzi o zmiennaDynamic

zmiennaDynamic.EndsWith("dynamic");

zmiennaDynamic.JakasFajnaMetodaNieistniejąca();

Możemy podać cokolwiek. To czy zadziała dowiemy się dopiero podczas uruchomienia. Po cóż takiego? Pomaga to w przypadku używania np. obiektów zdefiniowanych za pomocą komponentów COM.

using vs using(IDisposable)

Kolejna rzecz, która na początku wprawia w lekkie zakłopotanie szczególnie, gdy ktoś mówi, iż dane wywołanie należy umieścić w klauzuli using. Od razu pojawiają się pytania, zaraz zaraz czy using nie stosujemy aby dodać przestrzeń nazw do naszej aplikacji? Używamy, jednak using ma jeszcze drugie użycie, które wiąże się z typem IDisposable. Pomińmy zatem using w kontekście dodawania przestrzeni nazw.

using (FileStream stream = new FileStream("plik.txt",FileMode.Open))

{

 

}

Taka konstrukcja jest równoważna następującej.

FileStream stream = null;

try

{

    stream = new FileStream("plik.txt", FileMode.Open);

    //...

}

finally

{

    if (stream != null)

        stream.Close();

}

Widzimy, że w rozwiniętej formie przypisanie do zmiennej następuje w bloku try, a zamknięcie otwartego pliku w bloku finally. natomiast pierwsza forma jest dużo wygodniejsza i bardziej czytelna. Czy musimy tak robić z typami IDisposable? Prosta odpowiedź – nie. Bardziej złożona – nie, ale…ale musimy pamiętać, że możemy mieć wycieki pamięci. Ale jak to? Przecież w .NET jest GarbageCollector, który zwalnia za nas pamięć. Mając w pamięci to zdanie przechodzimy do ostatniego tematu.

Wycieki pamięci w .NET

Tak, tak. Mimo, że w .NET mamy GC, który dba o zwalnianie pamięci to wycieki są możliwe. Jak? GC dba tylko o zwalnianie zasobów zarządzanych (managed, choć też nie zawsze – o tym może kiedy indziej). Z tymi unmanaged sobie nie poradzi, gdyż nie będzie wiedział kiedy mogą być one zwolnione. Jakie to są zasoby unmanaged? Pliki, połączenia do bazy danych, pędzle (Brush) itp.. I po to jest zaimplementowany interface IDisposable, abyśmy mogli w przystępny sposób, za pomocą wywołania Dispose powiedzieć GC kiedy już danego zasobu nie potrzebujemy i kiedy go można zwolnić.
Trzeba uważać na ten interface, gdyż wiele początkujących osób łapie się na tym, że gdzieś kiedyś usłyszało, że w .NET wycieków pamięci być nie może i tkwią w takim przeświadczeniu dość długo.

Tyle jeśli chodzi o zauważone przeze mnie pytania od osób, które (z reguły) zaczynają przygodę z .NET. A jakie były wasze spostrzeżenia gdy zaczynaliście przygodę z .NET?

Jakby ktoś chciał obejrzeć filmy z sesji z kursu C# to są one dostępne na stronie Spis publikacji.

5 komentarze:

Anonimowy pisze...

Ja kiedy zaczynałem naukę programowania w C# miałem największe problemy z delegatami i eventami. Dodatkowo ciężko mi się było połapać czym właściwie różni się List od Ienumerable, poza tym że ten drugi to interfejs. Tyle "problemów" udało mi się przypomnieć na szybko.

Anonimowy pisze...

Zdecydowanie delegaty i eventy z tak zwanym "podpinaniem się pod zdarzenie"

Anonimowy pisze...

Coś w tym jest bo dla mnie Delegaty i Eventy nawet teraz (po 4 latach) nie są czymś co bym powiedział, że "w pełni rozumiem"

pawlos pisze...

A jakie konkretne problemy są z tymi delegatami i eventami? Może będzie temat na kolejny post?

Paweł

Anonimowy pisze...

Z delegatami to mniej więcej wiadomo, że to takie opakowanie na metody, czy transport jednej/kilku metod jednocześnie, "podpięcie" ich pod event. Z eventami już gorzej, bo ciężko to dobrze zrozumieć na aplikacjach konsolowych, a właśnie tak się ich uczy większość osób (tak mi się przynajmniej wydaje). Przydał by się dobry post nt. wzorca Publisher-Subscriber, jak do działa na przykładach i to najlepiej w konsoli bo nie każdy bawi się w WPFie czy Winformsach.