.NET i takie tam

Archive for Grudzień 2008

Dynamic Proxy

with one comment

Jednym z popularnych wzorców projektowych jest wzorzec Proxy. W skrócie, obiekt pełniący rolę proxy, implementuje interfejsy i zarządza, zgodnie z jakąś logiką, wywołaniami metod jakiejś innej klasy. Przykładowo, załóżmy, że mamy następujące klasy:

Kod:

public interface IMessageSender
{
    void Send(string message);
    void Send();
}

public class SimpleSender : IMessageSender
{
    public virtual void Send(string message)
    {
        Console.WriteLine("Send() - with praram: " + message);
    }
    
    public virtual void Send()
    {
        Console.WriteLine("Send() - no praram");
    }
}

public class Sender : IMessageSender
{
    private readonly IMessageSender _sender;
    
    public Sender(IMessageSender sender)
    {
        _sender = sender;
    }
    
    public void Send(string message)
    {
        // tutaj implementujemy dodatkową logikę sterującą wysyłaniem wiadomości
        Console.WriteLine("Send action controlled by Sender object.");
        _sender.Send(message);
    }
    
    public void Send()
    {
        // tutaj implementujemy dodatkową logikę sterującą wysyłaniem wiadomości
        Console.WriteLine("Send action controlled by Sender object.");
        _sender.Send();
    }
}

W powyższym przykładzie Sender jest klasą proxy dla klasy SimpleSender. Obie klasy implementują interfejs IMessageSender, przy czym tylko klasa SimpleSender ‚wie’ jak wysłać wiadomość. Obiekt proxy (Sender) implementuje dodatkową logikę sterującą wywołaniem poszczególnych metod klasy SimpleSender. Przykładowe wywołanie metody Send():

Sender proxy = new Sender(new SimpleSender());
proxy.Send();

Istnieje możliwość automatycznego generowania klas proxy w trakcie działania aplikacji – zwana Dynamic Proxy. Przy korzystaniu z tej techniki zwykle korzysta się z dodatkowych bibliotek jak Castle DynamicProxy czy LinFu.DynamicProxy. W poniższym przykładzie wykorzystałem tą ostatnią bibliotekę:

using LinFu.DynamicProxy;

public interface IMessageSender
{
    void Send(string message);
    void Send();
}

public class SimpleSender : IMessageSender
{
    public virtual void Send(string message)
    {
        Console.WriteLine("Send() - with praram: " + message);
    }
    
    public virtual void Send()
    {
       Console.WriteLine("Send() - no praram");
    }
}

public class MessageSenderInterceptor : IInvokeWrapper
{
    private readonly IMessageSender _sender;
    
    public MessageSenderInterceptor(IMessageSender sender)
    {
       _sender = sender;
    }
    
    public void BeforeInvoke(InvocationInfo info)
    {
       Console.WriteLine("We are about to call method: " + info.TargetMethod);
    }
    
    public object DoInvoke(InvocationInfo info)
    {
       return info.TargetMethod.Invoke(_sender, info.Arguments);
    }
    
    public void AfterInvoke(InvocationInfo info, object returnValue)
    {
    }
}

IMessageSender i SimpleSender są dokładnie takie same jak w poprzednim przykładzie, nowością jest klasa MessageSenderInterceptor implementująca interfejs IInvokeWrapper. Jak widać z implementacji definiuje on metody określające co się stanie przed, w trakcie i po tym jak w kodzie wywołamy metody zawarte w naszym bazowym interfejsie IMessageSender. Na koniec, sposób wywołanie metody Send():

var proxy = new ProxyFactory();
var senderInterceptor = new MessageSenderInterceptor(new SimpleSender());
var finalSender = proxy.CreateProxy<IMessageSender>(senderInterceptor);
finalSender.Send("Hello World!");

Wszystko prosto ładnie, tylko nasuwa się pytanie po co? Po co dodawać dodatkowe referencje do nowych bibliotek, po co korzystać z ProxyFactory i innych interfejsów, skoro można napisać to wszystko prosto samemu?

Fakt jeśli mamy jedną, dwie klasy implementujące interfejs IMessageSender których zachowanie chcielibyśmy w jakiś sposób zmodyfikować – to jasne, nie ma sensu bawić się w Dynamic Proxy.

Jeżeli natomiast klas tych jest więcej Dynamic Proxy zaoszczędzi nam sporo fatygi w implementowaniu obiektów proxy dla poszczególnych klas implementujących IMessageSender, nie mówiąc już o mniejszej ilości klas do przetestowania. Co więcej zastosowanie dowolnego Dependency Injection Framework pozwoli nam uczynić Dynamic Proxy niewidoczne dla programisty korzystającego z naszego kodu.

Written by sakowicz

Grudzień 16, 2008 at 1:52 pm

.NET CF – uwagi o pamięci/szybkości działania

4 komentarze

Microsoft ma swoje ‚best practices’ i ja mam swoje 😉 Generalnie to co przedstawię poniżej to taki własny, nieuporządkowany, ‚core dump’ różnych informacji, wygrzebanych gdzieś po zakamarkach różnych dokumentacji i poradników. Wskazówki, głównie, dotyczą problemów z pamięcią i szybkością działania aplikacji pracujących pod kontrolą .NET Compact Frameworka i systemów Windows Mobile.

  • Im większy rozmiar pliku *.exe tym wolniej aplikacja będzie się ładować. Gdzieś tam wyczytałem, że szybkość ładowania w przybliżeniu wynosi 1Mb na 1s. Samemu nie mierzyłem, wierzę na słowo. Główną przyczyną spowolnienia jest kontrola uprawnień. Aby zmniejszyć rozmiar możemy nie podpisywać naszego assembly, klucz publiczny swoje zajmuje (oczywiście nie zawsze może to być możliwe). Po drugie warto zrezygnować z umieszczania zasobów wewnątrz naszej aplikacji (embedded resources) i ładować je w miarę potrzeb w trakcie działania aplikacji.
  • Właściwości (properties) klas są traktowane, przez kompilator, jako odwołania do metod. Czasem więc lepiej skorzystać z publicznego pola, szczególnie w obiektach intensywnie wykorzystywanych w wszelkiego rodzaju pętlach. Z podobnych względów dobrze jest, do minimum, ograniczyć zastosowania takich słów kluczowych jak abstract czy virtual. Pamiętajmy jednak, że data binding czy LINQ operują jedynie na właściwościach.
  • Koszty wywoływania metod:
    • wywołanie metody z instancji klasy to w przybliżeniu 2-3x koszt wywołania funkcji z .NET CF (ciekawe czemu taka różnica?)
    • wywołanie metody wirtualnej to ok. 1.4x wywołania metody z instancji klasy zarządzanej
    • wywołanie metody przy pomocy mechanizmu P/Invoke to ok. 5x kosztu wywołania metody z instancji klasy zarządzanej.
  • Pętla foreach wywołuje metody wirtualne na kolekcji – szybciej będzie jak skorzystamy z pętli for.
  • Po zakończeniu pracy z kolekcją zadbajmy o jawne usunięcie jej elementów – zmniejszymy tym samym pracę jaką będzie musiał wykonać garbage collector.
  • Minimalizujemy ilość tworzonych śmieci. Pracując z łańcuchami znaków pamiętajmy o klasie StringBuilder.
  • Starajmy się zminimalizować operacje boxing/unboxing.
  • Nigdy nie korzystamy z operacji GC.Collect() – osobiście jeszcze nigdy nie spotkałem sytuacji wymagającej jej zastosowania. Zwykle kiedy chcemy ją wywołać jest już za późno i niepotrzebnie spowalniamy działanie naszej aplikacji. Garbage Collector aby wykonać swoje zadanie musi zatrzymać wszystkie wątki. .NET Compact Framework automatycznie wywołuje GC.Collect(); zanim wyrzuci OutOfMemoryException.
  • Obsługa wyjątków nie stanowi większego obciążenia, chyba, że sami rzucamy wyjątki (throw). Nigdy nie korzystamy z wyjątków do kontroli pracy aplikacji. Chwytamy tylko te wyjątki którym możemy zaradzić, całą resztę obsługujemy w zdarzeniu UnhandledException.
  • Ograniczamy korzystanie z mechanizmu refleksji. O ile wykorzystanie typeof(…) nie stanowi większego problemu, to dostęp do pamięci przy urzyciu Type.InvokeMember() jest już od 10 do 100 razy wolniejsze od wywołania zwykłej metody.
  • Pamiętajmy o nadpisaniu metod ToString(), GetHashCode(), Equals() – domyślne implementacje wykorzystują refleksję.
  • Praca z xml’em wymaga użycia refleksji.
  • Korzystanie z web services oznacza korzystanie xml’a – dobrze jest przechować referencję w pamięci i nie tworzyć ją za każdym razem gdy korzystamy z usługi.
  • Korzystając z statycznych form dobrze jest je utworzyć w tle np. przy starcie aplikacji, a dane przekazywać tuż przed ich wyświetleniem.
  • Jeśli generujemy zawartość formy w trakcie działania aplikacji pamiętajmy o korzystaniu z metod SuspendLayout/ResumeLayout.
  • Pamiętajmy operatorze -= przy pracy z wyjątkami, nie wiem jak z aktualną wersją .NET Compact Framework ale w starszych wersjach pozostawienie przypisanego zdarzenia, prowadziło do wycieków pamięci w trakcie zamykania formy.
  • Motoda Dispose() jest po to aby z niej korzystać po zakończeniu pracy z obiektem. Bardzo częstym problemem jest nie wywoływanie jej dla obiektu SqlCeCommand. Koszt wywołania operacji z bazą danych SQL Compact Edition w głównej mierze zależy od ilości danych a nie rodzaju zapytania. Im więcej danych tym więcej natywnych zasobów zajmuje obiekt SqlCeCommand ich nie zwolnienie prowadzi do wycieków pamięci. Pamiętajmy, klauzula using(…) jest naszym przyjacielem.
  • Korzystamy z obiektów SqlCeDataReader bądź w bardziej wymagających wypadkach SqlCeResultSet. Zapominamy o takich obiektach jak DataSet czy DataTable. Jedynym wyjątkiem jaki w tym momencie przychodzi mi na myśl jest zastosowanie Microsoft Synchronization Services for ADO.NET (przynajmniej, na tyle ich jeszcze nie poznałem aby stwierdzić czy nie da się inaczej).
  • SqlCeConnection – dyskusji o tym czy powinno się korzystać z jednego połączenia czy wielu, podczas pracy z aplikacją mobilną, toczyło się wiele. Generalnie jeśli naszym problemem jest szybkość aplikacji korzystamy tylko z jednego, cały czas, otwartego połączenia. Natomiast borykając się z brakiem pamięci, dobrze jest otwierać, zamykać i niszczyć połączenie w miarę potrzeb, gdyż alokuje ono pewną ilość zasobów natywnych.
  • Każda aplikacja pracująca w systemie Windows Mobile 6 ma do dyspozycji 32Mb przestrzeni adresowej i musi się w niej nasza aplikacja łącznie z bibliotekami .NET CF.
  • Każda dodatkowa biblioteka dołączona do naszej aplikacji, niezależnie od jej rozmiaru, zajmuje minimum 64Kb, więc jeśli korzystamy z dużej ilości małych plików dll (zwykle np. 10-20kb) może okazać się, że marnujemy dużą ilość przestrzeni adresowej.

Wiele z tych porad wydaje się całkiem oczywistych, niektóre jednak wydadzą się zapewne dziwne, wręcz przeczące zdrowemu rozsądkowi. Pamiętajmy, jednak , że piszę tutaj o nie tylko o okrojonej wersji .NET Framework’a ale również zmodyfikowanej do potrzeb i możliwości urządzeń mobilnych.

Zdarza się, że w niektórych prezentacjach/tutorialach (zwykle tych wprowadzających) autorzy piszą, że aplikacje mobilne pisze się tak samo jak aplikacje desktopowe. Jest to całkowicie, nieprawda, fakt .NET CF pozwala nam korzystać, w większości przypadków, z tych samych funkcji co desktopowy odpowiednik, jednak założenia i sposób implementacji, dobrej, aplikacji powinny być inne.

Written by sakowicz

Grudzień 2, 2008 at 10:30 am