czwartek, 28 lutego 2013

[C#|Visual Studio] C#: Konstrukcja dynamic i ExpandoObject

W języku C# od wersji 4.0 mamy do dyspozycji konstrukcję składniową zaczynającą się od słowa dynamic. Słowo to zapewnia dynamiczną typowalność, to znaczy, że typ takiej zmiennej zostanie ustalony nie na poziomie kompilacji (jak w przypadku słowa var), lecz w czasie pracy programu, stąd owa dynamiczność. Poniżej prosty przykład:

dynamic unknownVariable;
unknownVariable = "this is message";
Console.WriteLine(unknownVariable);
unknownVariable = DateTime.Now;
Console.WriteLine(unknownVariable);

Jeżeli zdarzy nam się, że na przykład do daty z powyższego kodu będziemy chcieli dodać liczbę, co nie jest dozwolonym działaniem, nie otrzymamy błędu kompilacji, ale wyjątek typu RuntimeBinderException, o którym dokumentacja MSDN mówi, że:
Represents an error that occurs when a dynamic bind in the C# runtime binder is processed.
Kolejnym ciekawym typem wykorzystywanym do dynamicznego typowania jest ExpandoObject. Obiekty takie pozwalają na dynamiczne typowanie pól obiektu, co znane jest z innych języków programowania, takich jak np. JavaScript.

dynamic expando = new ExpandoObject();
expando.val1 = "Another silly message";
expando.count = 911;
expando.Pi = 3.14;
foreach (var prop in expando)
{
    Console.WriteLine(prop.GetType());
    Console.WriteLine(prop);
}

Powyższy kod wypisze na konsoli następujące rzeczy:


Zatem widzimy, że ExpandoObject jest odpowiednim słownikiem, umożliwiającym składowanie obiektów dowolnego typu, ze względu na to, że w C# wszystko pochodzi od klasy object.
Aby pobrać pojedynczą wartość, wystarczy zrzutować obiekt dynamicznie typowany do słownika.

var singleValue = ((IDictionary<string, object>) expando)["val1"];

wtorek, 26 lutego 2013

[WPF] ExpressionBlend: SampleData

Tworząc w WPF interfejs użytkownika, często potrzebujemy przykładowych kolekcji danych, np. wyświetlanych w ListBoxie. Korzystając z Blenda, można w prosty sposób wygenerować kolekcje przykładowych danych, składających się z propercji różnych typów.Rozpocząć należy od zakładki Data, widocznej na przykład w prawym górnym rogu okna designera.


W drugim kroku należy dodać kolekcję i zbudować odpowiedni model danych, a więc dodać properties interesującego nas typu.






Jedną z fajniejszych opcji jest możliwość dodawania Stringów różnego typu, w zależności od potrzeb aplikacji.


Dane należy następnie podłączyć jako ItemSource...


a w ListBoxTemplate  zbindować odpowiednie kontrolki od odpowiednich propercji, wybierając z menu dostępnego pod prawym przyciskiem myszy opcję Data bind Content to Data..

Jeżeli to nie wystarcza, w każdej chwili można utworzyć przykładowe dane w oparciu o własny plik XML z danymi.


Po dodaniu przykładowych danych warto także spojrzeć do folderu SampleData, tworzonego przez Blenda. Tworzona jest automatycznie klasa wspierająca INotifyPropertyChanged, dzięki czemu wszystkie zmiany modelu danych będą odświeżały UI i na odwrót.

niedziela, 24 lutego 2013

[HTML|JS|CSS] HTML5: IndexedDb

Ciekawym trendem, jaki można zaobserwować w standardzie HTML5 jest możliwość składowania coraz większej ilości danych po stronie klienta. Przestarzałe ciasteczka zastępowane są przez znacznie lepszy mechanizm localStorage. To jednak nie wszystko. Pojawia się także możliwość składowania danych w dokumentowej bazie IndexedDb. Podobnie jak w WebStorage, dane zapisywane są dla danej strony internetowej i tylko z jej poziomu mogą być odczytane. Tym razem możemy jednak składować całe obiekty JavaScriptowe, a nie tylko ciągi znaków. Co więcej, zgodnie z dokumentacją IndexedDb, pojemność takiej bazy praktycznie ograniczona jest jedynie miejscem na dysku. Same zapytania wykonywane są w sposób asynchroniczny, programista zatem musi ich wynik obsłużyć w callbackach.

Pracę z indexedDb, technologią mocno eksperymentalną, nad którą prace cały czas trwają, warto rozpocząć od  przypisania sobie dla wygody każdej z możliwych implementacji bazy do jednej zmiennej.

window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;

Jak widać ostatnim silnikiem przeglądarek, który nie wspiera indexedDb jest Presto, na którym tworzona była Opera (obecnie opera ma także być tworzona w oparciu o webkit, więc problem znika dla nowszych wersji).

Zapisywanie obiektów do bazy odbywa się poprzez odpowiednie callbacki.

$("#store").click(function(){
 var value = $("#msg").val();
 var request = indexedDB.open("MyDb", 1); //1
 request.onsuccess = function (evt) {
  db = request.result;
  var transaction = db.transaction("messages", "readwrite"); //2
  var objectStore = transaction.objectStore("messages"); //3                   
  var req = objectStore.add({ date: new Date().toLocaleString(), message: value });
  req.onsuccess = function (evt) {
   console.log('successfully saved');
  };
 };
 request.onerror = function (evt) {
  console.dir(evt);
 };

 request.onupgradeneeded = function (evt) {
  //...
 };
});

Ważne są trzy linie:
  1. Otwieramy bazę danych o nazwie MyDb, opcjonalnie jako drugi parametr podajemy wersję schematu bazy, do której chcemy się łączyć
  2. Pierwszy argument może być także tablicą i jest listą kontenerów, na których będzie wykonywana transakcja
  3. Żeby dodać obiekt do pojedynczego kontenera potrzebujemy obiekt typu objectStore, o nazwie messages
Powyższy kod sprawdza się pod warunkiem, że mamy już utworzoną bazę i kontener, w przeciwnym wypadku wywołana zostanie funkcja onupgradeneeded. 

request.onupgradeneeded = function (evt) {
 var objectStore = evt.currentTarget.result.createObjectStore("messages", 
                             { keyPath: "id", autoIncrement: true 
                         });

    objectStore.createIndex("date", "date", { unique: false });
    objectStore.createIndex("message", "message", { unique: false });
};

Tworzymy objectStore o nazwie messages, posiadający unikalny autoinkrementujący się identyfikator. Dodatkowo, na potrzeby wyszukiwania, można utworzyć dwa indeksy.

Odczyt danych

Podczas odczytu wykorzystuje się indeksy, zwracające wartość i wskaźnik na następny element z kolekcji.

$("#restore").click(function(){
 var request = indexedDB.open("MyDb");
 request.onsuccess = function(evt){
  var db = request.result;
  var transaction = db.transaction("messages");
  var objectStore = transaction.objectStore("messages");
  var req = objectStore.openCursor();
  req.onsuccess = function(evt){
   var cursor = evt.target.result;
   if(cursor){
    var obj = cursor.value;
    var li = $("<li>");
    li.html(obj.date + ", " + obj.message);
    items.append(li);
    cursor.continue();
   }
  }
 }
});

Usuwanie

Korzystając z unikalnych indeksów, usuwanie staje się bardzo proste.Na przykład dla elementu o indeksie 1


$("#removeFirst").click(function(){
 var request = indexedDB.open("MyDb");
 request.onsuccess = function (evt) {
  db = request.result;
  var req = db.transaction(["messages"], "readwrite")
            .objectStore("messages").delete(1);
        }
});

Na koniec warto wspomnieć o tym, jak wygodnie można oglądać stan bazy w przeglądarce Google Chrome, w zakładce Resources.

piątek, 22 lutego 2013

[HTML|JS|CSS] HTML5: WebSockets

Poprzedni post wprowadził do tematyki WebSocketów oraz pokazał, jak prosto zaimplementować je po stronie serwerowej. Tym razem czas na stronę kliencką, gdzie dzięki API HTML5 możemy w prosty sposób wysyłać i odbierać wiadomości. Kluczową funkcją konstruktora jest WebSocket, gdzie jako parametr podaje się URI serwera z gniazdem. Do dyspozycji mamy cztery zdarzenia, do których należy dopisać callbacki. Są to:

  • open
  • message
  • error
  • close
Do komunikacji z serwerem mamy funkcje send i close.

var close = document.getElementById("close");
var send = document.getElementById("send");

socket = new WebSocket("ws://localhost:9001", "echo-protocol");

socket.addEventListener("open", function(event) {
  status.textContent = "Connected";
});

socket.addEventListener("message", function(event) {
  var element = document.createElement('li');
  element.innerHTML = event.data;
  ul.appendChild(element); 
});

socket.addEventListener("error", function(event) {
  alert('Error');
});

socket.addEventListener("close", function(event) {
  status.textContent = "Not Connected";
});


close.addEventListener("click", function(event) {
  message.textContent = "";
  socket.close();
});

send.addEventListener("click", function(event) {
  socket.send(text);
  text.value = "";
});

[HTML|JS|CSS] Node.js: WebSockets

WebSockety to ciekawa technologia wykorzystywana w modelu klient - serwer. Zasada działania jest bardzo prosta. Pomiędzy przeglądarką a serwerem otwierany jest dwukierunkowy kanał komunikacji. Obie strony mogą wysyłać wiadomości bez konieczności odpytywania się nawzajem, a także przetwarzać otrzymane wiadomości na zasadzie obsługi zdarzeń. Zgodnie ze specyfikacją, adresy uri dla websocketów zaczynają się od ws lub wss (dla połączeń szyfrowanych).

Implementując WebSockety w Node.js, najwygodniej jest skorzystać z modułu ws. Obsługa połączenia a także wiadomości tradycyjnie wykonywana jest w callbackach, przy czym należy pamiętać że callback dla wiadomości zagnieżdża się w callbacku obsługi połączenia.

var ws = require('ws');
var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({port: 9002});

wss.on('connection', function(ws) {
 ws.on('message', function(message) {  
  var date = new Date();
  var str = date.toLocaleDateString() + ' ' +
   date.toLocaleTimeString();

  var reversed = "";
  for (var i = message.length - 1; i >= 0; i--) {
   reversed += message[i];
  }
   ws.send(reversed);
  });

 var date = new Date();
 var str = date.toLocaleDateString() + ' ' +
  date.toLocaleTimeString();
 ws.send('WebSocket connected at: ' + str);
});

wtorek, 19 lutego 2013

[C#|Visual Studio] Threading: Tasks

Task to obiekt reprezentujący pewnną jednostkę pracy (fragment kodu), którą chcemy zrównoleglić. Korzysta on z puli wątków, co poprawia wydajność w stosunku do wątków uruchamianych przez klasę Thread. Głównym zadaniem Tasków jest optymalne wykorzystanie wielordzeniowości komputera, przy niskich kosztach pamięciowych zrównoleglania (możemy dodawać do puli wątków setki zadań, które zostaną wykonywane w najszybszy możliwy sposób). Co więcej wprowadzają kilka zupełnie nowych, bardzo ciekawych funkcjonalności, takich jak na przykład możliwość uruchamiania child - Tasków z poziomu danego Tasku.

Operacje poprzez Taski można wykonywać na kilka sposobów:

static void RunTasks()
{
    //1 delegate
    Task.Factory.StartNew(() => Console.WriteLine("Hello from a task!"));
    //2 Task<T> - return value
    Task<string> task = Task.Factory.StartNew<string>(() =>
    {
        using (var wc = new System.Net.WebClient())
            return wc.DownloadString("http://www.agh.edu.pl");
    });
    //3 constructor
    var task2 = new Task(() => Console.Write("Hello"));
    //wait for result
    Console.WriteLine(task.Result);
    task2.Start();
    //4 state object
    var task3 = Task.Factory.StartNew (Greet, "Hello");
    task3.Wait();  // Wait for task to complete.   
}

static void Greet (object state) { Console.Write (state); }

Startowanie child-Tasków:

Task parent = Task.Factory.StartNew(() =>
{
    Console.WriteLine("I am a parent");

    Task.Factory.StartNew(() =>
    {
        Console.WriteLine("I am detached");
    });

    Task.Factory.StartNew(() =>
    {
        Console.WriteLine("I am a child");
    }, TaskCreationOptions.AttachedToParent);
});

Dzięki opcji AttachedToParent, aby parent mógł zakończyć swoje zadanie, będzie musiał poczekać na wykonania childa z tą właśnie opcją.

Synchronizację z Taskami można wykonać na dwa sposoby: wywołując metodę Wait() z opcjonalnym timeoutem, lub pobierając wartość property Result. W obu przypadkach program musi zaczekać na zakończenie działania Tasków.

Wyjątki:

Wyjątki w kodzie Tasków są propagowane do wątku wywołującego taski i opakowane w specjalną klasę AggregateException.

static void ExceptionTest()
{
    int x = 0;
    Task<int> calc = Task.Factory.StartNew(() => 7 / x);
    try
    {
        Console.WriteLine(calc.Result);
    }
    catch (AggregateException aex)
    {
        Console.Write(aex.InnerException.Message);  // Attempted to divide by zero
    }
}

Wyjątki z child-Tasków propagowane są do parent Tasków.

Continuation

Metoda ContinueWith z klasy Task pozwala na wykonywanie podanego delegata po zakończeniu działania zadania.

Task task1 = Task.Factory.StartNew(() => Console.Write("antecedant.."));
Task task2 = task1.ContinueWith(ant => Console.Write("..continuation"));

Synchronizować można się także z wieloma Taskami stosując ContinueWithAll bądź ContinueWithAny.
 

poniedziałek, 18 lutego 2013

[C#|Visual Studio] Threading: klasa Parallel

Kolejną ciekawą funkcjonalnością wprowadzoną w .NET 4.0 jest klasa Parallel. Udostępnia ona trzy metody umożliwiające zrównoleglenie wykonywania kodu.
  • Invoke - wykonuje równolegle tablicę delegatów
  • For - pętla for wykonywana równolegle
  • Foreach - pętla foreach wykonywana równolegle
Metody te zapewniają wysoką wydajność przy zrównoleglaniu obliczeń, ponieważ wykorzystują mechanizm Tasków, zatem np. gdy podamy sto delegatów, to zostaną one rozpartycjonowane na taką ilość tasków, by zapewnić maksymalną efektywność.Wykonywania każdej z tej funkcji zakończy się, gdy skończą działanie wszystkie taski.

static void InvokeTest()
{
    Parallel.Invoke(() => new WebClient().DownloadString("http://www.agh.edu.pl/"),
        () => new WebClient().DownloadString("http://www.eaiib.agh.edu.pl/"));
}

static void ForTest()
{
    var keyPairs = new string[6];
    Parallel.For(0, keyPairs.Length,
                  i => keyPairs[i] = RSA.Create().ToXmlString(true));
    foreach (var keyPair in keyPairs)
        Console.Write(keyPair);
}

static void ForeachTest()
{
    var arr = new int[]
                  {
                      166, 147, 159, 156
                  };
    Parallel.ForEach(arr, 
        s => Console.WriteLine("Alfa Romeo {0}", s));
}

Metoda For jest przeładowana na wiele sposobów. Jeżeli potrzebujemy, by wykonywać skomplikowane operacje matematyczne, możemy skorzystać z wersji metody, w której możemy wykorzystać lokalną zmienną, inicjalizowaną przez jednego z delegatów.

static void ForLockerTest()
{
    object locker = new object();
    double grandTotal = 0;

    Parallel.For(1, 10000000,
      () => 0.0,
      (i, state, localTotal) =>
         localTotal + Math.Sqrt(i),
      localTotal =>
      { lock (locker) grandTotal += localTotal; }
    );

    Console.WriteLine(grandTotal);
}