Category Archives: C#

Typ dynamic w C# 4.0

W wersji 4.0 wprowadzono nowy typ – dynamic. Zmienne tego typu będą sprawdzane w momencie wywołania (runtime) a nie kompilacji. Oznacza to, że w czasie kompilacji nie będziemy poinformowani o błędach takich jak brak funkcji. Przykład:

dynamic variable="tekst";
variable=variable *5;

Powyższy kod skompiluje się, jednak w momencie uruchomienia zostanie wyrzucony wyjątek (nie można pomnożyć przecież tekstu przez liczbę). Gdybyśmy użyli klasycznych typów (np. string) kompilacja nie powiodłaby się.

Dynamic jest wykorzystywany w najnowszej wersji (3.0) ASP.NET MVC np. jako ViewBag – upraszcza to składnie. Wykorzystanie dynamic również eliminuje konieczność wykorzystywania refleksji w momencie gdy mamy obiekt typu object i chcemy wywołać jakąś metodę czy konstruktor (a nie mamy bazowego interfejsu). Należy oczywiście stosować typ ostrożnie ponieważ często jego użycie może oznaczać błędną architekturę.

Sprawdzanie czy metoda jest podpięta do danego zdarzenia

Czasami chcemy mieć pewność, że zdarzenie nie zostanie podpięte dwa razy do tej samej metody. Przyjrzyjmy się jak to zrobić w C#:

private EventHandler _Event;

public event EventHandler ExampleEvent
{
  add
  {
      if (_Event == null || !_Event.GetInvocationList().Contains(value))
          _Event += value;
  }

  remove
  {
      _Event -= value;
  }
}

Rozwiązaniem jest użycie metody GetInvocationList. Następnie podłączając metody do zdarzenia za pomocą += mamy pewność, że nie będzie duplikatów i metoda nie będzie wywoływana kilkukrotnie.

Metoda generyczna oraz przeładowana nakładka na nią z parametrem Type

Załóżmy, że mamy metodę generyczną:

private void ShowView<T>(object pars)

Teraz chcemy zdefiniować nakładkę na nią, która zamiast parametru T przyjmowałaby Type:

private void ShowView(Type type, object pars)

Dodatkowym wymogiem jest niedublowanie logiki zwartej w tej metodzie. Aby wywołać wersję generyczną z niegenerycznej należy użyć mechanizmu refleksji:

private void ShowView(Type type, object pars)
{
    MethodInfo methodInfo=GetType().GetMethod("ShowView");
    MethodInfo genericMethod= methodInfo.MakeGenericMethod(type);
    genericMethod.Invoke(this,new object[]{pars});
}

Liczba miejsc po przecinku w decimal

Pisząc kod odpowiedzialny za walidację liczb potrzebowałem sprawdzić ile miejsc po przecinku ma dany decimal. Użyłem rozwiązania może mało eleganckiego ale przynajmniej działającego:) :

decimal decimalNumber = 21.235;
int length = (decimalNumber % 1).ToString().Length - 2;

Reszta z dzielenia przez jeden zawsze zwraca to co jest po przecinku. Dla 21.235 będzie to 0.235. Zatem długość string’a minus dwa ( jeden dla przecinka, jeden dla zera) stanowi liczbę miejsc po przecinku.

Może komuś się to przyda w przyszłości :).

Pętla (ciekawostka)

Pewien kolega podesłał mi dziś ciekawy fragment kodu (znaleziony na jakimś blogu):

int level = 10;
Func<int, int> nextLevel = new Func<int, int>(x => x--);
Func<int, bool> loopAgain = new Func<int, bool>(x => x >= 0);

while (loopAgain(level))
{
    level = nextLevel(level);
}

Pytanie: ile razy wykona się pętla? Odpowiedzi nie będę zdradzał. Zawsze można odpalić i przekonać się samemu:).

W jaki sposób interpretowane są wyrażenia lambda?

Kiedyś czytając książkę “More Effective C#” zaciekawiło mnie wyjaśnienie interpretacji wyrażeń lambda przez kompilator. W książce autor przedstawił następujący fragment kodu:

public class ModFilter
{
    private readonly int modulus;
    public ModFilter(int mod)
    {
        modulus = mod;
    }
    public IEnumerable<int> FindValues(
        IEnumerable<int> sequence)
    {
        return from n in sequence
               where n % modulus == 0
               select n * n; 
    }
}

W rzeczywistości wyrażenie zostanie skonwertowane do znanych już od dawna delegat:

public class ModFilter
{
    private readonly int modulus;
    
    private bool WhereClause(int n)
    {
        return ((n % this.modulus) == 0);
    }
    
    private static int SelectClause(int n)
    {
        return (n * n);
    }
    
    private static Func<int, int> SelectDelegate;
    public IEnumerable<int> FindValues(
        IEnumerable<int> sequence)
    {
        if (SelectDelegate == null)
        {
            SelectDelegate = new Func<int, int>(SelectClause);
        }
        return sequence.Where<int>(
            new Func<int, bool>(this.WhereClause)).
            Select<int, int>(SelectClause);
    }    
}

Kompilator wygenerował metody WhereClause oraz SelectClause. Ciekawszą jednak obserwacją jest przypadek w którym wykorzystujemy zmienne lokalne:

public class ModFilter
{
    private readonly int modulus;    
    public ModFilter(int mod)
    {
        modulus = mod;
    }
    public IEnumerable<int> FindValues(
        IEnumerable<int> sequence)
    {
        int numValues = 0;
        return from n in sequence
               where n % modulus == 0               
               select n * n / ++numValues;
    }
}

Zmienna numValues jest lokalna. W jaki sposób zostanie to zinterpretowane? Otóż kompilator wygeneruje klasę zagnieżdżoną:

public class ModFilter
{
    private sealed class Closure
    {
        public ModFilter outer;
        public int numValues;
        public int SelectClause(int n)
        {
            return ((n * n) / ++this.numValues);
        }
    }
    private readonly int modulus;
    public ModFilter(int mod)
    {
        this.modulus = mod;
    }
    private bool WhereClause(int n)
    {
        return ((n % this.modulus) == 0);
    }
    public IEnumerable<int> FindValues
        (IEnumerable<int> sequence)
    {
        Closure c = new Closure();
                c.outer = this;
        c.numValues = 0;
        return sequence.Where<int>
            (new Func<int, bool>(this.WhereClause))
            .Select<int, int>(
                new Func<int, int>(c.SelectClause));
    }
}

Closure posiada dostęp do zewnętrznej klasy ModFilter. Wszystkie zmienne lokalne (w tym przypadku numValues) zostały po prostu przekopiowane. Używając wyrażeń lambda łatwo zapomnieć o tym, że tak naprawdę tworzone są osobne metody i wszystkie parametry lokalne muszą zostać przekopiowane. Wyrażenia znacznie ułatwiają i przyśpieszają pisanie kodu ale z drugiej strony równie łatwo można popełnić błąd. Autor książki (Bill Wagner) przytacza następujący kod jako przestrogę:

int index = 0;
Func<IEnumerable<int>> sequence =
    () => Utilities.Generate(30, () => index++);
index = 20;
foreach (int n in sequence())
    Console.WriteLine(n);
Console.WriteLine("Done");
index = 100;
foreach (int n in sequence())
    Console.WriteLine(n);

Kod wydrukuje wartości od 20-50 a następnie od 100-130. Według autora może to być zaskakujące ponieważ funkcja definiowana jest dla index=0. Jednak ze względu na to sposób interpretacji (closure) wartość zmiennej jest przekopiowywana i generowanie nie zaczyna się od 0 a od 20 oraz 100. Moim zdaniem jest to akurat bardzo intuicyjne, aczkolwiek należy zdawać sobie sprawę ze sposobu realizacji lambda w C#.

Różnica miedzy IEnumarable a IQueryable (LINQ to SQL)

Interfejsy IEnumerable oraz IQueryable mogą wydawać się bardzo podobne. W końcu IQueryable implementuje IEnumerable więc funkcjonalność musi być podobna. W praktyce poniższe dwa zapytania bardzo się różnią:

IEnumerable<Customer> customers = context.Customers.Where(c => c.FirstName.StartsWith("J"));
customers = customers.Take<Customers>(5);

IQueryable<Customer> customers = context.Customers.Where(c => c.FirstName.StartsWith("J"));
customers = customers.Take<Customers>(5);

Pierwsze zapytanie jest skrajnie niewydajne. Najpierw zostaną wybrani wszyscy klienci z imieniem zaczynającym się od litery J a dopiero potem lokalnie zostanie zwróconych pierwszych 5 wierszy. Jeśli zatem tabela zawiera milion wierszy to wszystkie  te wiersze zostaną odczytane z bazy danych. W SQL wyglądałoby to następująco:

select * from Customers WHERE FirstName like 'j%';

W pamięci powstanie tablica zawierająca milion elementów. Następnie metoda Take zwróci pierwszych 5 elementów.

W przypadku interfejsu IQueryable zapytanie wygląda juz w pełni optymalnie tzn:

select top(5) * from Customers WHERE FirstName like 'j%';

Należy zwrócić uwagę na top(5) – z bazy danych zostaną odczytane wyłącznie te wiersze, które tak naprawdę chcemy. Z przykładów widać, że bardzo łatwo napisać niewydajny kod. Wszelkie wywołania IEnumerable są wykonywane lokalnie i nie zostaną przetworzone bezpośrednio w zapytanie SQL.

Singleton a wielowątkowość

Z racji tego, że w ostatnim czasie sporo pisałem o wielowątkowości w C#, dzisiaj pokaże prawidłową implementacje wzorca projektowego singleton przystosowanego do pracy w środowisku współbieżnym. Na początek przyjrzyjmy się klasycznej implementacji:

public sealed class Singleton
{
    private static Singleton m_Instance = null;
    
    private Singleton() { }
    
    public static Singleton Instance
    {
        get
        {
            if(m_Instance == null)
                m_Instance = new Signleton();
            return m_Instance;
        }
    }
}

Wyobraźmy sobie sytuację w której dwa wątki jednocześnie próbują utworzyć instancję klasy. Może zdarzyć się, że wątki jednocześnie wejdą do if’a sprawdzającego m_Instance. W takim przypadku zostaną utworzone dwie instancje Signleton. Jak temu zaradzić?

Wystarczy użyć jednego z mechanizmów synchronizacji opisanych we wcześniejszych postach. Polecam lock – w przypadku synchronizacji wewnątrz AppDomain jest to przeważnie najlepsze rozwiązanie.

public sealed class Singleton
{
    private static Singleton m_Instance = null;
    private static readonly object m_Sync = new object();
    
    private Singleton() { }
    
    public static Singleton Instance
    {
        get
        {
            lock(m_Sync)
            {
                if(m_Instance == null)
                    m_Instance = new Signleton();
            }
            return m_Instance;
        }
    }
}

Powyższa implementacja jest poprawna, jednak ma jedną wadę – za każdym razem trzeba zakładać blokadę co w środowisku rozproszonym może spowodować istotny spadek wydajności. Spróbujmy więc zakładać blokadę wyłącznie gdy m_Instance jest równy null (czyli podczas pierwszego wywołania właściwości Instance):

public sealed class Singleton
{
    private static Singleton m_Instance = null;
    private static readonly object m_Sync = new object();
    
    private Singleton() { }
    
    public static Singleton Instance
    {
        get
        {
            if(m_Instance == null )
            {
                lock(m_Sync)
                {
                    if(m_Instance == null)
                        m_Instance = new Signleton();
                }
            }
            return m_Instance;
        }
    }
}

Z kodu widzimy, że synchronizujemy kod tylko gdy instancja nie została jeszcze utworzona. Po zainicjowaniu obiektu kod działa już w pełni optymalnie, zwracając po prostu obiekt Singleton.

Jeśli nie zależy nam na tzw. lazy loading(opóźnione ładowanie) możemy stworzyć instancję obiektu w momencie deklaracji m_Instance:

public sealed class Singleton
{
    private static Singleton m_Instance = new Singleton();
    
    private Singleton() { }
    
    public static Singleton Instance
    {
        get
        {            
            return m_Instance;
        }
    }
}

Implementacja jest w pełni optymalna oraz bezpieczna w środowisku współbieżnym. Jedyną wadą jest fakt, że Signleton będzie utworzony(podczas np. dostępu do innych statycznych pól klasy) nawet w przypadku gdy  z niego nie będziemy korzystać w programie. Jeśli wiemy, że za każdym razem podczas działania aplikacji będziemy wywoływać klasę to powyższe rozwiązanie może okazać się bardzo dobre.

Czas na najlepsze moim zdaniem rozwiązanie:

public sealed class Singleton
{   
   private Singleton() { }

   public static Singleton Instance
   {
       get
       {
           return Child.m_Instance;
       }
   }
   public static int TestValue { get; set; }
   class Child
   {
       private Child() { }
       internal static readonly Singleton m_Instance = new Singleton();
   }        
}

Przedstawiona implementacja jest optymalna ponieważ nie zakładamy żadnych blokad. Ponadto jest w pełni “leniwa”- inicjujemy obiekt dopiero gdy chcemy mieć do niego dostęp. Jeśli przypiszemy jakąś wartość właściwości TestValue, obiekt Singleton  nie zostanie utworzony – w przypadku wcześniejszej implementacji jakikolwiek dostęp do statycznych właściwośći powodował inicjalizację Singleton. Rozwiązanie jest oczywiście bezpieczne w środowisku współbieżnym.

Wielowątkowość(pętle, Task) w C# 4.0

Programowanie współbieżne w c# 4.0 jest znacznie łatwiejsze w porównaniu z poprzednią wersją. Widać, że platforma .NET staje się coraz dogodniejszym środowiskiem programistycznym dla rozwiązań równoległych.

Zacznijmy od pętli foreach. Przeważnie wykonujemy ją w sposób sekwencyjny. Jeśli chcielibyśmy zrównoleglić ją, musielibyśmy stworzyć instancję Thread i zawartość pętli umieścić w wątkach. Ponadto proces wymagałby użycia np. semafora albo ManualResetEvent aby zsynchronizować kod wykonywany po zakończeniu pętli. Na szczęście w wersji 4.0 mamy do dyspozycji gotowy mechanizm:

string []tab=new string[]{"a","b","c","d","e"};
System.Threading.Tasks.Parallel.ForEach<string>(tab, Method);

gdzie Method:

private static void Method(string val)
{
}

Pętla zostanie wykonana w sposób równoległy. Jeśli tylko nie współdzielimy jakiego stanu pomiędzy kolejnymi iteracjami, warto zastanowić się nad tym typem pętli,  ponieważ jest to bardzo proste a na procesorach wielordzeniowych wzrost wydajności może być znaczący.

Nową klasą w C# 4.0 jest System.Threading.Tasks.Task. Umożliwia ona wykonanie operacji w tle(w osobnym wątku). Warto podkreślić, że Task bazuje na ulepszonym ThreadPool, zatem wydajność tworzenia nowego Task’a jest dużo wyższa niż w przypadku “ciężkiego” Thread. Samo stworzenie Task’a jest analogiczne do Thread:

  Task t1 = new Task(() => Console.Write("task 1"));
  t1.Start();    

Możemy również poczekać aż task zostanie wykonany:

Task t1 = new Task(() => Console.Write("task 1"));
t1.Start();
t1.Wait();

Ciekawszym jednak rozwiązaniem jest tzw. kontynuacja zadań. Możemy stworzyć listę zadań, które będą wykonywały się po kolei. Często mamy przecież problemy współbieżne w których wykonanie kolejnego etapu musi być poprzedzone zakończeniem poprzedniego. Za pomocą ContinueWith możemy w łatwy sposób stworzyć taki plan wykonania:

Task t1 = new Task(delegate()
 {                    
     Console.Write("task 1");
     Thread.Sleep(5000);
 }
);
Task t2 = t1.ContinueWith((t) => Console.Write("task 2"));
t1.Start();
t2.Wait();

Powyższy kod gwarantuje, że zadanie t2 zostanie wykonane dopiero po zakończeniu t1.

Synchronizacja wątków(AutoResetEvent, ManualResetEvent, Interlocked), część 3

W celu synchronizacji wątków można wykorzystać mechanizm zdarzeń: ManualResetEvent oraz AutoResetEvent. Rozwiązanie polega na zastosowaniu sygnalizacji. Chcąc wejść do sekcji krytycznej piszemy:

ManualResetEvent resetEvent = new ManualResetEvent(false);
resetEvent.WaitOne();

W konstruktorze ustawiamy początkową wartość sygnału na false(brak sygnału). Następnie wywołujemy metodę WaitOne, która czeka na nadejście sygnału. Metoda blokuje kod aż  do momentu gdy w jakimś miejscu kodu zostanie wysłane zdarzenie za pomocą metody Set:

resetEvent.Set();

AutoResetEvent działa bardzo podobnie – jedyną różnicą jest fakt, że po otrzymaniu sygnału w WaitOne, stan zostaje ponownie ustawiony na false. Rozważmy dwa fragmenty kodu z ManualResetEvent oraz AutoResetEvent:

ManualResetEvent resetEvent = new ManualResetEvent(true);
resetEvent.WaitOne();
resetEvent.WaitOne();
MessageBox.Show("Hello world"); // zostanie wykonane

AutoResetEvent resetEvent = new AutoResetEvent(true);
resetEvent.WaitOne();            
resetEvent.WaitOne();
MessageBox.Show("Hello world"); // nigdy nie zostanie wykonane

W przypadku ManualResetEvent, WaitOne sprawdza tylko sygnał i nie modyfikuje go. Zatem wiadomość MessageBox zostanie wyświetlona. W przypadku AutoResetEvent, kod zostanie zablokowany na drugim wywołaniu WaitOne ponieważ  pierwsze wykonanie WaitOne ustawi sygnał na false.

Interlocked jest bardzo wydajnym narzędziem(statyczną klasą) umożliwiającym wykonywanie podstawowych operacji takich jak inkrementacja czy zamiana dwóch liczb w sposób atomowy czyli bezpieczny z punktu widzenia współbieżności. Konkretnie do dyspozycji mamy następujące metody:

Add

Dodanie dwóch liczb ze sobą.

CompareExchange

Porównanie ze sobą dwóch liczb i w przypadku gdy są one równe zastąpienie pierwszej z nich wartością dostarczoną w drugim parametrze.

Decrement

Zmniejszenie wartości o jeden.

Exchange

Zamiana wartości zmiennych.

Increment

Inkrementacja(zwiększenie o jeden).

Read

Przydatne na procesorach 32 bitowych gdzie operacje czytania zmiennych 64-bitowych nie jest atomowa.

Przykład użycia:

int value = 5;
Interlocked.Increment(ref value);
System.Diagnostics.Debug.Assert(value == 6);

W następnej, ostatniej części napiszę o kilku  nowościach w .NET 4.0, zapraszam.