Category Archives: C#

Synchronizacja wątków(semafor, mutex), część 2.

W dzisiejszym poście przedstawię zasadę działania semafora oraz mutexa. Zacznijmy od teorii, czym jest semafor i jak można go zaimplementować? Otóż semafor jest sposobem na realizację wzajemnego wykluczania – zapewnienia, że tylko określona liczba wątków będzie mogła jednocześnie wykonać dany fragment kodu. Wyróżniamy semafory binarne, które dopuszczają maksymalnie jeden wątek oraz semafory ogólne, które umożliwiają jednoczesny dostęp określoną przez programistę liczbę wątków.

Implementacja semafora wymaga zaprogramowania dwóch metod: wait oraz signal. Programista wchodząc do sekcji krytycznej wywołuję wait a wychodząc wykonuje signal. Zatem kod źródłowy semafora ogólnego mógłby wyglądać następująco:

// ustawienie wartośći początkowej semafora
counter=5;

// metody atomowe
void Wait()
{
    while(counter<=0){}
    counter--;
}
void Signal()
{
    counter++;
}

Jeśli zasoby aktualnie są używane, metoda Wait będzie blokować dalsze wykonywanie kodu. Dopiero w momencie gdy sekcja krytyczna zostanie opuszczona(wywołanie Signal), warunek w Wait zostanie spełniony i dalszy kod będzie mógł się wykonać. Należy zaznaczyć, że metody muszą być atomowe ponieważ w przeciwnym razie mogą wystąpić problemy synchronizacyjne opisane w poprzednim poście. W przypadku gdy counter posiada wartość początkową równą 1, mamy do czynienia z semaforem binarnym.

Powyższy kod pokazałem tylko po to aby rozjaśnić sposób działania semafora. C# posiada bowiem  gotową klasę System.Threading.Semaphore:

System.Threading.Semaphore semaphore = new System.Threading.Semaphore(1, 1);
semaphore.WaitOne();
// sekcja krytyczna
semaphore.Release(1);

Konstruktor przyjmuje kolejno aktualną oraz maksymalną wartość licznika counter.

Przejdźmy teraz do następnego mechanizmu synchronizacji – Mutex’a. Mutex od strony użytkownika wygląda bardzo podobnie do semafora. Umożliwia jednak synchronizację na poziomie procesów a nie tylko wewnątrz AppDomain.

Należy również wspomnieć o zasadzie  “principle of ownership” – tylko wątek który nałożył blokadę może ją później zdjąć. W przeciwieństwie do semaforów, nie możemy ustawić blokady w wątku A a zdjąć jej w wątku B – zakończy się to wyrzuceniem wyjątku.

Klasycznym przykładem wykorzystania mutexów jest zapewnienie, że tylko jedna instancja programu zostanie uruchomiona:

class Program 
{ 
    static void Main(string[] args) 
    { 
        Mutex oneMutex = null; 

        const string MutexName = "SingleInstance";
        try 
        {         
            oneMutex = Mutex.OpenExisting(MutexName); 
        } 
        catch (WaitHandleCannotBeOpenedException) 
        { 
            // Mutex nie istnieje, obsługa wyjątku
        }         
        if (oneMutex == null)         
        {         
            oneMutex = new Mutex(true, MutexName); 
        } 
        else 
        {         
            oneMutex.Close(); 
            return; 
        } 
        // tworzenie okna itp.
    } 
}

Z przykładu widać, że podczas tworzenia obiektu Mutex można przekazać jego nazwę, która służyć będzie do rozpoznawania obiektu w różnych procesach. W przypadku gdy funkcja OpenExisting zwróci wartość różną od NULL(Mutex już utworzony), program zakończy działanie ponieważ zostanie wykonana instrukcja “return;”.

Oczywiście Mutex posiada również metody WaitOne oraz ReleaseMutex. Sposób użytkowania jest niemalże identyczny jak w przypadku semafora binarnego, więc nie będę pokazywał już kodu źródłowego.

W następnym poście planuje opisać klasy ManualResetEvent, AutoResetEvent oraz Interlocked – zapraszam do odwiedzenia bloga za kilka dni.

Synchronizacja wątków w C# (lock, Monitor), część 1

Najtrudniejszym zadaniem w programowaniu współbieżnym jest programowanie sekwencyjne a uściślając synchronizacja wątków;). Pewne operacje w naszych programach muszą być wykonywane w sposób sekwencyjny. Często dostęp do danych współdzielonych nie może odbywać się w sposób równoległy. Rozważmy klasyczny problem zwiększania liczby o jeden:

counter = counter + 1;

Jeśli zmienna counter jest współdzielona przez kilka wątków, powyższa  operacja jest niepoprawna. Dlaczego? Zacznijmy od początku. Zwiększanie liczby o jeden, tak naprawdę składa się z trzech operacji:

  1. Wczytanie zmiennej do rejestru procesora.
  2. Zwiększenie wartości w rejestrze o jeden.
  3. Przekopiowanie wartości rejestru z powrotem do zmiennej.

Załóżmy, że counter ma wartość 10. Wątek T1 chce zwiększyć wartość i wczytuje ją do rejestru. Następnie wątek T2 również chce zwiększyć wartość i kopiuje ją do rejestru(wciąż counter==10), zwiększa o jeden i przenosi wynik z powrotem do zmiennej. Zmienna zatem w tej chwili ma już wartość 11. Wątek T1 jednak w swoim rejestrze ma nieaktualną wartość 10. Bez synchronizacji, wątek T1 zwiększy wartość rejestru o 1 i otrzyma nieprawidłowy wynik 11(powinno być 12). Jeśli zamotałem, to mam nadzieję, że poniższy pseudokod rozjaśni problem:

;Wątek T1
Time1:    Kopiowanie wartości do rejestru.
Time4:    Zwiększenie rejestru o jeden. ;rejestr w tej chwili ma już nieaktualną wartość.
Time5:    Przekopiowanie rejestru do zmiennej counter.

;Wątek T2
Time1:    Kopiowanie wartości do rejestru.
Time2:    Zwiększenie rejestru o jeden.
Time3:    Przekopiowanie rejestru do zmiennej counter.

W programowaniu równoległym nie można zakładać, że wątki pracują w tym samym tempem. Każda operacja może trwać dłużej lub krócej w zależności od rdzenia procesora, priorytetu itp.

Zatem wszelkie operacje podzielne powinny być wykonywane w specjalnych obszarach kodu do których dostęp ma tylko jeden wątek jednocześnie –  tzw. sekcjach krytycznych.

C# posiada wiele mechanizmów synchronizacji. Zacznijmy od najpopularniejszego – operatora lock:

class Example
{
    private object m_SyncObject=new object();
    public void ThreadMethod()
    {    
        lock(m_SyncObject)
        {
            // sekcja krytyczna
        }
    }
}

Jak widać pewnie  ułatwienie dla synchronizacji posiada już sam język C# dostarczając słowo kluczowe lock. Niezbędnym jest dostarczenie obiektu, który stanowi pewne odniesienie dla synchronizacji. Za pomocą operatora lock blokujemy dostęp do dostarczonego obiektu (w tym przypadku m_SyncObject). Jeśli drugi wątek będzie chciał w tym samym momencie wejść do obszaru lock, będzie musiał poczekać aż pierwszy skończy wszelkie operacje zawarte w klauzuli lock. Wejście do obszaru lock z tym samym obiektem synchronizującym (m_SyncObject) możliwe jest wyłącznie przez jeden wątek .

Warto zaznaczyć, że nie powinno się używać referencji this oraz operatora lock:

class Example
{    
    virtual public void ThreadMethod()
    {    
        lock(this)
        {
            // sekcja krytyczna
        }
    }
}

Powyższy kod wywoła blokadę (deadlock)  w sytuacji gdy użytkownik klasy napiszę np.:

Example example=new Example();
lock(example)
{
    example.ThreadMethod();
}

Pierwszy lock(example) zadziała, jednak następny w ThreadMethod już nie zostanie nadany ponieważ referencje this oraz example odnoszą się do tego samego obiektu i tym samym lock(this) będzie czekał aż lock(example) zostanie zwolniony(co nie nastąpi nigdy).

Projektując biblioteki nie możemy przewidzieć w jaki sposób przyszli użytkownicy będą korzystać z naszej klasy więc powinniśmy pisać w sposób jak najbardziej elastyczny.

Następnym mechanizmem jest klasa Monitor. W rzeczywistości jest ona tym samym co lock. Słowo kluczowe lock  zostało wprowadzone po to aby programiści nie musieli pisać poniższego kodu:

class Example
{
   private object m_SyncObject = new object();
   public void ThreadMethod()
   {
       System.Threading.Monitor.Enter(m_SyncObject);
       try
       {
           // sekcja krytyczna
       }
       finally
       {
           System.Threading.Monitor.Exit(m_SyncObject);
       }
   }
}

Zwróćcie uwagę na try i finally. Bez tego awaria sekcji krytycznej(wyjątek) mogłaby spodować błędne zakończenie(brak wywołania metody Exit). Polecam wszystkim jednak używanie lock ponieważ trudniej w nim o popełnienie błędu takiego jak np. brak wywołania Exit. Warto jednak przyjrzeć się metodzie Monitor.TryEnter, która pozwala na sprawdzenie czy wejście do sekcji krytycznej jest możliwe.

W następnych postach planuje opisanie klas Mutex, Semaphore, AutoResetEvent, ManualResetEvent, Interlocked oraz o kilku nowościach wprowadzonych w .NET 4.0.

Wydajność wątków w C#

W języku C# mamy kilka mechanizmów tworzenia wątków. Różnią się one zarówno wydajnością jak i przeznaczeniem.

Zacznijmy więc od najpopularniejszego sposobu a mianowicie klasy System.Threading.Thread. Stworzenie wątku polega na inicjalizacji klasy oraz wywołania metody Start:

public class ThreadExample
{
    public CreateThread()
    {            
        System.Threading.Thread thread = new System.Threading.Thread(ThreadMethod);
        thread.Start(null);
    }
    private void ThreadMethod(object parameters)
    {
        while(true)
        {
            // jakiś kod
        }
    }
}

ThreadMethod zawiera kod, który chcemy wykonać współbieżnie. Warto także wspomnieć o właściwości Thread.IsBackground. Gdy ustawiamy ją na true(domyślnie jest false), wtedy wątek stanie się tzw. “background thread” – wątkiem zależnym od rodzica. W sytuacji gdy użytkownik zamknie naszą aplikację, automatycznie zostaną zamknięte wszystkie wątki “background”. Gdybyśmy nie ustawili IsBackground na true, po zamknięciu aplikacji, stworzone przez nas wątki nadal by pracowały, uniemożliwiając tym samym prawidłowe zamknięcie programu.

Najważniejszą jednak rzeczą, którą chciałem poruszyć w poście jest wydajność. Tworzenie klas Thread jest bardzo czasochłonne. O ile nie piszemy programu z naprawdę dużą ilością wątków, to podejście polegające na użyciu klasy Thread jest jednym z najgorszych rozwiązań. Związane jest to z długim czasem wymaganym na inicjalizacje tej klasy. Użycie Thread jest dobre gdy tworzymy pule wątków, które działają przez cały czas działania aplikacji.

Gdy tworzymy często nowe wątki, znacznie lepszym rozwiązaniem jest skorzystanie z gotowej puli wątków, które czekają już zainicjowane na użycie:

public class ThreadPoolExample
{
    public ThreadPoolExample()
    {        
        System.Threading.ThreadPool.QueueUserWorkItem(ThreadPoolMethod);
    }
    private void ThreadPoolMethod(object state) 
    {
        while(true)
        {
            // jakiś kod
        }
    }
}

Wywołując metodę QueueUserWorkItem system sprawdza czy w puli wątków jest wolny wątek .Jeśli tak, zwraca nam go i przechodzi do wykonywania kodu współbieżnego. Wątki zatem nie są tworzone za każdym razem od nowa – zawsze zwracane nam są już gotowe wątki do użycia. Gdy skończymy wykonywanie kodu współbieżnego, wątek nie zostaje zniszczony a wyłącznie zwrócony z powrotem do puli.

Dodatkowo warto wspomnieć, że wszystkie wątki z puli są typu “background”.

Gdy tworzymy interfejs użytkownika i chcemy jakaś operację wykonać w tle(np. połączenie się z WCF) warto zastanowić się nad klasą  BackgroundWorker. Przede wszystkim jest ona bardzo łatwo w użyciu(można ją nawet przeciągnąć z toolbox’a jako zwykłą kontrolkę) oraz posiada kilka zdarzeń, które są istotne dla GUI – np. powiadomienie o postępie prac. Krótki przykład:

public partial class Form1 : Form
{
   public Form1()
   {
       InitializeComponent();
       BackgroundWorker backgroundWorker = new BackgroundWorker();
       backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
       backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
       backgroundWorker.RunWorkerAsync();
   }

   void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
   {
       // e.ProgressPercentage
   }

   void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
   {
       while (true)
       {
           // jakiś kod kod
       }
   }        
}

Jeśli chcemy wykonywać pewną operacje co jakiś czas np. aktualizację lokalnej bazy danych z danymi otrzymywanymi przez usługę sieciową warto użyć timer’a:

public partial class Form1 : Form
{
   public Form1()
   {
       InitializeComponent();
       System.Threading.Timer timer = new System.Threading.Timer(ThreadMethod,null,0,1000);
       
   }
   private void ThreadMethod(object state)
   {
        // jakiś kod
   }
}

Jak widać ten specjalny timer znajduje się w przestrzeni nazw System.Threading. Metoda ThreadMethod będzie wykonywana współbieżnie co określony czas, przekazany w ostatnim parametrze konstruktora.

Wartość NULL oraz typy nie będące referencjami

W programach często wykorzystujemy wartość NULL do określenia np. czy operacja została prawidłowo wykonana. Niestety w przypadku typów “VALUE” nie można wykorzystać bezpośrednio wartości NULL ponieważ przetrzymują one wartość a nie adres komórki w pamięci.

Jeśli jednak mimo wszystko chcemy wykorzystać do porównania NULL, istnieje taka możliwość poprzez strukturę Nullable:

Nullable<int> value = 5;
value = null;
System.Diagnostics.Debug.Assert(value == null);

Ponadto c# dostarcza pewien skrót składniowy i zamiast pisać Nullable<int>wystarczy int?:

  int? value = 5;
  value = null;
  System.Diagnostics.Debug.Assert(value == null);

Optymalizacja klasy String

Wbudowany mechanizm optymalizacji może czasami przynieść zaskakujące wyniki. Rozważmy poniższy fragment kodu:

string var1 = "text";
string var2 = "text";

bool condition = object.ReferenceEquals(var1, var2);

Wydawałoby się, że var1 i var2 stanowią dwie osobne referencje. Po uruchomieniu kodu przekonamy się jednak, że zmienna condition będzie miała wartość true. Spowodowane jest to wykonaną optymalizacją, polegającą na tym, że .NET przechowuje zbiór użytych w programie napisów. Deklarując  zmienną przechowującą napis “text” pierwszy raz, .NET zapamiętuje napis w specjalnym buforze. Następnie gdy zostanie zadeklarowana ponownie ta sama wartość(“text”), nie nastąpi ponowna alokacja pamięci, a zwykłe przypisanie wskaźnika na wcześniej zapisaną wartość w buforze. Struktura adresowania wygląda więc mniej więcej tak:

image

Niestety nie zawsze może nastąpić optymalizacja. Poniższy kod nie zostanie zoptymalizowany i wartość condition będzie miała wartość false:

string var1 = "text";
string var2 = "tex";
var2 += "t";

bool condition = object.ReferenceEquals(var1, var2);

Przechowywanie poufnych informacji

W kodzie często używamy klasy Stirng do przechowywania poufnych informacji takich jak np. hasło. Niestety często nie zdajemy sobie sprawy jak niebezpieczne jest takie rozwiązanie. String jest specjalnie zoptymalizowaną klasą przeznaczoną do przechowywania łańcucha znaków, która jednak nie jest odporna na wszelkie ataki związane z podglądaniem pamięci operacyjnej. W skrócie pisząc, wszelkie informacje klasa String trzyma w postaci jawnej. Korzystając więc z odpowiedniego oprogramowania, intruz może bez problemu podejrzeć co aplikacja trzyma w klasie String. Ponadto ze względów na wspomnianą optymalizację String posiada następujące wady(w kwestii bezpieczeństwa):

  1. Brak możliwości wyczyszczenia bufora String. Nawet gdy przypiszemy wartość NULL, napis pozostaje wciąż zapisany w pamięci.
  2. Garbage Collector może robić kopie wartości String. Napisy nie są przechowywane ciągle pod tym samym adresem.
  3. Brak jakiejkolwiek metody szyfrowania danych.

W środowisku .NET rozwiązaniem na powyższe problemy jest klasa System.Security.SecureString. Moim zdaniem jest dosyć niewygodna w użyciu, jednak i tak wartość z niej korzystać.

Przykład zapisania do SecureString wartości podanej przez użytkownika(4 znaki):

SecureString secureString = new SecureString();

secureString.AppendChar(Console.ReadKey().KeyChar);
secureString.AppendChar(Console.ReadKey().KeyChar);
secureString.AppendChar(Console.ReadKey().KeyChar);
secureString.AppendChar(Console.ReadKey().KeyChar);

Następnie powinno się zablokować dostęp do wszelkich modyfikacji:

secureString.MakeReadOnly();

Wszelkie próby modyfikacji od tej chwili zakończą się wyrzuceniem wyjątku InvalidOperationException.

Metody częściowe(partial methods)

Z klasami częściowymi większość programistów c# prawdopodobnie miała już styczność. Klasycznym przykładem jest rozbicie klasy Form na część wygenerowaną przez Visual Studio oraz na część przeznaczoną do modyfikacji przez programistę. Metody częściowe są bardzo podobnym mechanizmem. W skrócie są to metody, które można definiować w dwóch różnych plikach. Zdecydowałem się o nich napisać ponieważ wydaje mi się, że są mniej znane, a czasami mogą okazać się przydatne.

Sama deklaracja jest analogiczna do klas częściowym. W jednym pliku deklarujemy metodę:

// Plik A
partial void Method();

W drugim pliku możemy zdefiniować ciało metody:

// Plik B
partial void Method()
{
    MessageBox.Show("Metoda czesciowa");
}

Mechanizm pozwala nam na definicję metody w innym pliku niż jej deklaracja. Metody częściowe mają jedną wyróżniającą cechę – wszelkie wywołania są usuwane na etapie kompilacji w przypadku gdy programista nie zdefiniował ciała tej metody. Dzięki temu kod jest optymalny ponieważ w sytuacji gdy nie było potrzeby definiowania metody, wszelkie wywołania będą usuwane w czasie kompilacji, co skutkuje, że plik końcowy nie będzie zawierał żadnych śladów tej metody.

Metody częściowe najczęściej wykorzystuje się w generatorach kodu. W takim przypadku nie chcemy aby użytkownik kodu modyfikował kod automatycznie wygenerowany(mogło by to zakłócić poprawne działanie tworzonego narzędzia programistycznego). Z drugiej jednak strony, chcemy aby użytkownik mógł modyfikować w jakimś stopniu działanie klasy. Możemy więc zdefiniować pewne metody częściowe, które będą wywoływane w odpowiednich momentach. Użytkownik klasy chcąc wpłynąć na działanie klasy mógłby definiować ciała metod częściowych. Oczywiście podobny rezultat można osiągnąć poprzez dziedziczenie i metody wirtualne, jednak metody częściowe są znacznie bardziej optymalne i przeważnie łatwiejsze w użyciu. Wszystko zależy jednak od konkretnego przypadku użycia – metody częściowe stanowią dodatek a nie próbę zastąpienia dotychczasowych rozwiązań.

Prawidłowe zwalnianie zasobów

W języku c# obiekt, którego zasobami chcemy sami zarządzać, powinien implementować interfejs IDisposable. Sporo osób aby zwolnić zasoby pisze następujący kod:

public class MyClass : IDisposable
{
   #region IDisposable Members

   public void Dispose()
   {
       // zwalnianie zasobow
   }

   #endregion
}

Interfejs niestety wymusza nam tylko implementację metody Dispose. Powyższe rozwiązanie jest zdecydowanie nieprawidłowe. Zanim  jednak przejdę do omawia co w kodzie jest niepoprawnego, podam prawidłową implementację:

public class MyClass:IDisposable
{
    private bool IsDisposed=false;
    public void Dispose()
    {
        Dispose(true);
        GC.SupressFinalize(this);
    }
    protected void Dispose(bool Diposing)
    {
        if(!IsDisposed)
        {
            if(Disposing)
            {
                // zwalniaj zasoby zarządzalne
            }
            // zwalniaj zasoby niezarządzalne
        }
        IsDisposed=true;
    }
    ~MyClass()
    {
        Dispose(false);
    }
}

Powyższa implementacja gwarantuje nam, że gdy obiekt będzie automatycznie czyszczony przez GarbageCollector to zasoby niezarządzane i tak zostaną wyczyszczone. Uzyskujemy to dzięki implementacji destruktora, który wywołuje się zawsze gdy GC zwalnia obiekt.  Właśnie w przypadku gdy to GC zwalnia obiekt a nie użytkownik ręcznie, metoda Dispose nie jest wywoływana więc jedynym miejscem na zrobienie tego jest destruktor.

Następna kwestia to przypadek gdy to sam użytkownik chce zwolnić obiekt. W tym przypadku musimy poinformować GC, że bierzemy na siebie odpowiedzialność za obiekt(metoda SupressFinalize) i tym samym destruktor nie zostanie wywołany(co uchroni nas przed zapętleniem).

Ponadto dodałem flagę IsDisposed, która umożliwia nam np. wyrzucenie wyjątku w przypadku gdy użytkownik drugi raz chce zwolnić obiekt.

Jak dodać opis wartości ENUM?

Czasami typ ENUM znajduje zastosowanie(czasami ponieważ często ogranicza on modułowość aplikacji). W wielu przypadkach potrzebujemy jednak skojarzyć pewien opis z każdą wartością enum’a. Jako praktyczny scenariusz można wymienić implementację menedżera dźwięków. Dla przykładu w pewnej grze, którą współtworzyłem aby uatrakcyjnić interfejs dla programisty zdefiniowałem sobie typ enumeryczny SOUND_TYPE:

public enum SOUND_TYPE
{
   ROCKET_LAUNCH,
   MACHINE_GUN_LAUNCH
}

Programista zatem chcąc wykorzystać dźwięk przedstawiający strzał rakiety wywołuje następującą metodę:

SoundManager.Instance.PlaySoundEffect(SOUND_TYPE.ROCKET_LAUNCH);

Dla użytkownika kodu wprowadzenie typu ENUM jest bardzo korzystne ponieważ wywołując metodę PlaySoundEffect nie da się wprowadzić błędnego parametru wejściowego. Powyższa definicja ENUM nie definiuje jednak w żaden sposób ścieżek plików dźwiękowych. W tym celu stworzyłem atrybut przypisujący każdemu typowi ENUM ścieżkę prowadzącą do pliku z dźwiękiem:

public enum SOUND_TYPE
{
   [AssetName(AssetName = "rocket_gun_launch")]
   ROCKET_LAUNCH,
   [AssetName(AssetName = "machine_gun_launch")]
   MACHINE_GUN_LAUNCH
}

Deklaracja AssetName to:

public class AssetNameAttribute:Attribute
{
   public string AssetName
   {
       get;
       set;
   }        
}

Mamy więc zdefiniowany typ ENUM oraz przypisaną ścieżkę pliku. Pozostaje nam tylko dostać się do tych danych. W tym celu zdefiniowałem metodę rozszerzającą(extensions, c# 3.0):

public static class EnumEx
{
   static public attr GetAttributeValue<attr>(this Enum primaryEnum) where attr:class
   {
       FieldInfo field = primaryEnum.GetType().GetField(primaryEnum.ToString());
       Attribute[] attrs = field.GetCustomAttributes(typeof(attr), false) as Attribute[];
       if (attrs.Length == 0) return null;
       else
           return attrs[0] as attr;
   }
}

Teraz aby odczytać ścieżkę do pliku dla podanego enum’a wystarczy:

SOUND_TYPE soundType = SOUND_TYPE.ROCKET_LAUNCH;
string filePath = soundType.GetAttributeValue<AssetNameAttribute>().AssetName;

Scenariuszy jest naprawdę wiele na wykorzystanie powyższego rozwiązania. Dla przykładu wyobraźmy sobie, że implementujemy CRM i mamy listę telefonów. W ENUM możemy zdefiniować sobie typy HOME, WORK  itp. Następnie za pomocą własnego atrybutu możemy dodać opis każdej kategorii, który będzie wyświetlany w interfejsie. Za pomocą atrybutów możemy także definiować reguły walidacyjne dla zmiennych(RangeAttribute, RequiredAttribute, DefaultAttribute itp).

Na koniec pozostaje mi wyjaśnienie 2 kwestii.

Po pierwsze zawsze powinniśmy zastanowić się czy aby na pewno warto wprowadzać typ ENUM do budowanego systemu. Wracając do przykładu CRM, co jeśli pewnego dnia okaże się, że wymagana jest trzecia grupa telefonów np. MOBILE? Wprowadzanie jej wymagało by modyfikacji kodu co w aplikacjach klasy enterprise jest niedopuszczalne. Najlepiej podsumowuje ten wniosek, jedno z podstawowych zasad w inżynierii oprogramowania Open\Closed Principle:

A module should be open for extension but closed for modification”

Druga to wytłumaczenie się dlaczego zdecydowałem się użyć SOUND_TYPE w grze skoro tak neguje większość przypadków użycia ENUM. Wynika to po prostu z przyjętych wymagań, technologii oraz czasu na wykonanie gry. Gra została napisana w XNA, która wykorzystuje tzw. Content Pipiline. Nie wchodząc w szczegóły wszelkie zasoby gry(dźwięki, modele, efekty) muszą być przekompilowane i zamienione na specjalny format obsługiwany zarówno na PC jak i XBOX. Skoro zasoby nie mogą być dodawane dynamicznie przez użytkowników(ponieważ muszą być rekompilowane) to po co męczyć się z rozszerzalnym interfejsem, który i tak nigdy nie zostanie wykorzystany?

Prawidłowe definiowanie wyjątków w c#

Programując własne biblioteki, często potrzebujemy zdefiniować własny typ wyjątku. Przeglądając różnego rodzaju kody źródłowe nierzadko spotykam błędną deklarację własnych wyjątków:

 public class MyException : Exception
 {
    // specyfikacja
 }

Co prawa kompilator nie zgłosi błędu ale już np. CodeAnalysis zwróci nam uwagę o błędnej deklaracji. Najprościej korzystać w Visual Studio z tzw. snippet’ów czyli gotowych fragmentów kodu. Naciskając klawisze ctrl+space pojawi nam się lista dostępnych snippetów w VS:

snippetsWybieramy oczywiście Exception a automatycznie zostanie wstawiony poniższy fragment kodu:

    [Serializable]
    public class MyException : Exception
    {
        public MyException() { }
        public MyException(string message) : base(message) { }
        public MyException(string message, Exception inner) : base(message, inner) { }
        protected MyException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }

Jak widać deklaracja jest bardziej złożona ponieważ prawidłowa implementacja wyjątku wymaga uwzględnienia mechanizmu serializacji czyli zapisu klasy np. do pliku XML. Potrzebne jest to do wysyłania klasy zdalnie przez sieć.Wyobraźmy sobie sytuacje w której nasza biblioteka zostaje wyeksponowana za pomocą WCF. W takim przypadku jeśli chcemy zwracać wyjątki do klienta muszą one być zapisane do jakiegoś formatu(np. XML) i zwrócone zdalnie klientowi.