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.