Poniżesz można ściągnąć materiały z 42. spotkania TGD.NET
To będzie pierwszy wpis na temat interfejsu IDisposable w tej krótkiej mini serii z większej serii.
Otóż dobry programista używający jakiegokolwiek obiektu, który implementuje interfejs IDisposable jest zobowiązany do wywołania metody Dispose w momencie gdy skończy pracować z daną instancją klasy. Spowoduje to zwolnienie zasobów przez wykorzystywany obiekt, np. zwrócenie połączenia bazodanowego do puli połączeń, rozłączenie połączenia tcp/ip, zwolnienie jakiegoś niezarządzalnego zasobu czy zwolnienie dużej wewnętrznej struktury.
W bazowym frameworku .Neta niestety możemy spotkać kilka niekonsekwentnych i problematycznych implementacji. Przyjęło się poprzez konwencję, że na niektórych klasach ( np. FileStream ) dostępne są dwie metody: Close i Dispose. Czym one się różnią? Otóż dokładnie niczym.
public virtual void Close() { this.Dispose(true); GC.SuppressFinalize((object) this); } [__DynamicallyInvokable] [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public void Dispose() { this.Close(); }
Zatem czy jest różnica, którą metodę można wołać?
Tak, jest.
Jeśli napiszemy kod, który będzie przypominał poniższy to mamy duży problem. Jaki?
FileStream file = File.Open("processMe.txt", FileMode.Open); // Process file here file.Close();
Otóż w momencie gdy w bloku procesującym poleci wyjątek to metoda Close nie zostanie wywołana i co za tym idzie plik nie zostanie zwolniony. Sam .Net w późniejszym czasie to naprawi (tak, by nie było wycieków niezarządalnych zasobów) poprzez finalizator (o ile jest zaimplementowany). Jednak jest to niedeterministyczne zachowanie na którym polegać nie wolno. Każdemu dobremu programiście nie przytrafi się taka sytuacja, gdyż wie, że należy obiekty implementujące interfejs IDisposable używać wraz z klauzulą using.
using(FileStream f = File.Open("processMe.txt", FileMode.Open)) { // Process file here }
Jeżeli popatrzymy sobie do disassemblera to zobaczymy jak powyższa konstrukcja jest rozwijana.
Dostrzeżemy tam użycie metody Dispose oraz bloku try finally. W momencie gdy poleci wyjątek blok obsługi błędów wykona sprzątanie zasobów i wszystko będzie w porządku.
Powyższy kod otworzony w Reflectorze z wyłączonymi optymalizacjami zostanie zdekompilowany do następującej postaci:
FileStream f; bool CS$4$0000; f = File.Open("processMe.txt", 3); Label_000D: try { goto Label_0021; } finally { Label_0011: if ((f == null) != null) { goto Label_0020; } f.Dispose(); Label_0020:; } Label_0021: return;
Co ciekawe są w .Netcie klasy, które implementują interfejs IDisosable w sposób explicit. Oznacza to, że metoda Dispose nie będzie normalnie widoczna w IntelliSense uniemożliwiając nam szybkie stwierdzenie, czy klasa implementuje wspomniany interfejs i użycie using. W takich przypadkach ja od razu pisz using, a kompilator mi powie, czy jest tam implementacja IDisposable.
Jakie to klasy?
Dziś zajmiemy się operatorami związanym z rzutowaniem typów. W przykładach będziemy używać następującej hierarchii:
public class Animal { } public interface IMammal { } public class Tiger : Animal, IMammal { }
Operator is służy do sprawdzenia, czy dany obiekt jest konkretnego typu, bądź czy implementuje wskazany interfejs.
object tiger = new Tiger(); if ( tiger is Animal) { Animal animal = (Animal) tiger; } if ( tiger is IMammal) { IMammal mammal = (IMammal) tiger; }
Natomiast operator as rzutuje dane obiekt o ile jest on określonego typu, w przeciwnym wypadku zwraca null. Tutaj warto zauważyć, że możemy tylko rzutować na typy, które są nullowalne – nie możemy używać struktur (value type’ów).
object tiger = new Tiger(); Animal animal2 = tiger as Animal; if ( animal2 != null) { // Do something } IMammal mammal2 = tiger as IMammal; if (mammal2 != null) { // Do something }
NHibernate i query only property
Natrafiłem ostatnio na ciekawą i przydatną możliwość mapowania.
Wyobraźmy sobie następującą sytuację. Mamy dwie encje: Customer i Order.
public class Order { public int Id { get; set; } public decimal TotalPrice { get; set; } public Customer Customer { get; set; } } public class Customer { public int Id { get; set; } public string Name { get; set; } }
W strukturze obiektowej potrzebujemy tylko relację Order.Customer, natomiast nie chcemy mieć kolekcji Customer.Orders. Tylko jak teraz wybrać klientów, którzy nie mają zamówień?
Otóż da się to prosto uczynić wykorzystujac query only property. Zobaczmy na plik mapujący.
<class name="Order" table="Orders"> <id name="Id" column="Id"> <generator class="native"/> </id> <property name="TotalPrice" /> <many-to-one name="Customer" column="CustomerId" cascade="all" not-null="true"/> </class> <class name="Customer" table="Customers"> <id name="Id" column="Id"> <generator class="native"/> </id> <property name="Name" /> <bag name="Orders" access="none"> <key column="CustomerId" /> <one-to-many class="Order"/> </bag> </class>
Przy definicji mapowania kolekcji po stronie Customera używamy access=”none” albo access=”noop”. W naszym modelu ta relacja nie będzie widoczna, ale będzie mogli pisać zapytania przy jej użyciu. Poniższe wybieramy klientów, którzy mają co najmniej jedno zamówienia.
using (var s = sessionFactory.OpenSession()) { var customers = s.CreateQuery("from Customer c where size(c.Orders) > 0") .List(); foreach (var customer in customers) Console.WriteLine(customer.Name); }
Nowy artykuł o async i await właśnie się ukazał. Zapraszam do lektury.
“Długo oczekiwana funkcjonalność, która rozwiązuje problem, z jakim spotkał się każdy developer tworzący aplikację z GUI – blokowanie i zawieszanie się interfejsu użytkownika. Od teraz obsługa kontynuacji operacji asynchronicznych została wbudowana w sam język, co znacząco uprościło obsługę tego typu sytuacji.”
C# pod lupą – params
Dzisiaj zajmiemy się kolejnym ciekawym dodatkiem w C# – słówkiem kluczowym params.
Do metody można przekazać kolekcję parametrów np. poprzez tablicę:
public string CombinePath(string[] parts) { return string.Join( "", parts ); }
Oczywiście użycie tej metody nie będzie bardzo komfortowe, gdyż trzeba zapisać tworzenie tablicy.
var path = CombinePath(new [] { "c:", "Window", "system32"});
Jeśli dodamy do naszego parametru słówko params (dostępne tylko na ostatnim parametrze), to będziemy mogli wykonywać funkcję jakby miała zmienną liczbę argumentów:
public string CombinePath(params string[] parts) { return string.Join( "", parts ); }
var path = CombinePath("c:", "Window", "system32");
Oczywiście nadal możemy przekazać tablice, np. poprzez kod infrastrukturalny.
Pamiętajmy, by następnym razem tworząc API uwzględnić ten syntactic sugar w adekwatnych miejscach, poprawiając czytelność kodu, jak i jego długość.
C# pod lupą – @
Dziś pod lupę weźmiemy małpę. Małpa występuje w C# w 2 miejscach
- Pozwala użyć zastrzeżonej nazwy jako nazwy zmiennej, np.
string @event = "text";
Oczywiście normalnie nie zalecam używać takich zmiennym. Przydaje się ona jednak bardzo w momencie gdy generujemy kod, np. przy użyciu templatów T4. Nie musimy się wtedy martwić, czy jakaś nazwa nie spowoduje błędów kompilacji.
-
Drugie zastosowanie to escapowanie całej ścieżki w zmiennej tekstowej. Zamiast pisać
string path = "C:\\Users\\Michael\\Pictures\\Downloaded Albums";
możemy napisać po prostu
string path = @"C:\Users\Michael\Pictures\Downloaded Albums";
Przydatna sztuczka, gdy wklejamy ścieżkę ze schowka.
Komentarze
Dziś chciałem podzielić się ciekawą sztuczką z użyciem komentarzy. Pozwala ona na szybkie przełączenie dwóch bloków kodu. Oto ona:
//* Block /*/ Block2 //*/
Block jest instrukcją, która jest kompilowana, natomiast Block2 to komentarz.
Jeśli wykomentujemy pierwszy znak ‘/’ Block będzie komentarzem, a Block2 kodem.
Gdzie to może się przydać? Hmm, ja osobiście używam tego podczas szkoleń i prezentacji. Bardzo często wzbudza ona zaciekawienie uczestników. Uważam, że przdaje się ona także podczas prototypowania, gdy chcemy sprawdzić dwa warianty kodu lub testowania.
Jak ustawić DependencyProperty programowo? Otóz odpowiedź na to pytanie wydaje się bardzo prosta – użyć funkcji SetValue:
public void SetValue( DependencyProperty dp, Object value )
Niestety wymaganiem jest tu posiadanie instancji DependencyProperty.
Co jednak jeśli chcemy ustawić property znając tylko jego ścieżkę, np. “RenderTransform.Children[0].Angle”. By poradzić sobie z tym problemem można skorzystać z poniższej klasy.
public static class DependencyPropertyHelper { private static readonly DependencyProperty DummyProperty = DependencyProperty.RegisterAttached( "Dummy", typeof(Object), typeof(DependencyObject), new UIPropertyMetadata(null)); private static readonly DependencyObject _object = new DependencyObject(); public static void SetValue(object container, string dependencyProperty, object value) { Binding binding = new Binding(dependencyProperty) { Source = container, Mode = BindingMode.TwoWay }; BindingOperations.SetBinding(_object, DummyProperty, binding); _object.SetValue(DummyProperty, value); #if SL BindingOperations.SetBinding(_object, DummyProperty, null); #elif WPF BindingOperations.ClearBinding(_object, DummyProperty); #endif } public static object GetValue(object container, string dependencyProperty) { Binding binding = new Binding(dependencyProperty) { Source = container }; BindingOperations.SetBinding(_object, DummyProperty, binding); return _object.GetValue(DummyProperty); } }
Wykorzystuje ona pewien trick – używa bindingu do pobierania i ustawiania property.
Linq to potężne narzędzie, dzięki któremu można zrobić złożone rzeczy małą ilością kodu. Oto przykład:
XDocument doc = XDocument.Load("data.xml"); var r = from page in doc.Element("prizes").Elements().AsParallel() .WithDegreeOfParallelism(16) .WithExecutionMode(ParallelExecutionMode.ForceParallelism) from prize in page.Elements("item") let points = prize.Element("points").Value select new Price() { Category = page.Element("header").Value .Substring("Nagrody w kategorii: ".Length), Name = prize.Element("title").Value, Content= prize.Element("content").Value, ImagePath = GetImage(prize.Element("image").Value), Points = Convert.ToInt32( points.Substring(0,points.Length-3).Replace(" ", "")) };
Co robi powyższe zapytanie? Otóż z pewnego xml’a wybiera ono wszystkie elementy będące nagrodami (tag item) i mapuje je na postać obiektową. Co ciekawe metoda GetImage pobiera obrazek z internetu skojarzony z nagrodą. Niestety sekwencyjne wykonanie jest stosunkowo wolne, bo jest to zewnętrzna operacja IO. Korzystając z PLinq możemy to bardzo łatwo i szybko zmienić. Wystarczy użyć extension metody AsParallel. Od tej wchili analiza, mapowanie i ściąganie obrazków odbywać się będzie rownolegle dla kilku nagród. Czy nie jest to proste?