Ostatni natknąłem się na problem testowania zdarzeń. Sprawa niestety nie jest prosta na pierwszy rzut oka. Zdarzenia w aplikacji wywołują się asynchronicznie (nie wiemy dokładnie kiedy zdarzenie zajdzie) więc aby je przetestować musimy poczekać aż dane zdarzenie wystąpi i wtedy zweryfikować poprawność danych. Tak więc nasuwa się pytanie jak wstrzymać metodę testową aby czekała do momentu zajścia zdarzenia lub jeśli jest to wymagane zasygnalizowała nam, że czas na wykonanie zadania minął.
Bardzo przydatna w takich przypadkach okazuje się klasa Monitor i instrukcja lock. Cały mechanizm opiera się na zablokowaniu obiektu w metodzie testującej za pomocą metody Monitor.Wait i czekanie na odblokowanie go przez zdarzenie metoda Monitor.Pulse. Taka funkcjonalność w zupełności wystarcza na potrzeby testów jednostkowy, a prostota sprawia, że testy tworzymy w szybki sposób. Przyjdźmy do przykładu i zobaczmy jak to wszystko wygląda w praktyce.
[csharp]
[TestMethod()]
public void MyExampleEventTest()
{
MyApplication target = new MyApplication();
bool result = false;
target.TestEvent += (s, e) =>
{
//Do something
result = s is MyApplication
lock (this)
{
Monitor.Pulse(this);
}
};
int timeout = 1000;
lock (this)
{
if (!Monitor.Wait(this,timeout)) //Wait for Event
Assert.Fail("Time out");
}
Assert.IsTrue(result);
}
[/csharp]
W przedstawionym przykładzie wykorzystałem funkcję Wait która po określonym czasie kończy działanie. Oczywiście możemy użyć też innej wersji funkcji Wait np. bez podawania czasu, dzięki czemu metoda testujące będzie czekać do skutku czyli do momentu wystąpienia zdarzenia.
Interfejs klasy — w obiektowych językach programowania interfejs jest abstrakcyjną reprezentacją klasy pozwalającą na korzystanie z niej niezależnie od faktycznej implementacji. Interfejs pozwala na enkapsulację wielu różnych obiektów utworzonych w oparciu o odmienne klasy, które zawierają implementację wspólnego interfejsu. [wikipedia]
Interfejs dostarcza nam pewną funkcjonalność którą realizują klasy implementujące go, dzięki czemu możemy posługiwać się wyłącznie interfejsem aby zrealizować konkretne zadania bez konieczności zagłębiania się w kod znajdujący się za nimi.
Interfejsy w C#
W języku C# definiując interfejs posługujemy się słowem kluczowym interface, trzeba pamiętać że przy tworzeniu interfejsu nie używamy modyfikatorów dostępu, gdyż wszystkie elementy zawarte w interfejsie są publiczne. W interfejsach tworzymy jedynie definicje wybranych elementów które muszą zaimplementować klasy/struktury je implementujące.
Interfejsy mogą zawierać deklarację:
metod
właściwości
zdarzeń
indekserów
Interfejsy nie mogą zawierać stałych, pól, operatorów, konstruktorów, destruktorów i zagnieżdżonych typów. Nie mogą zawierać również statycznych elementów.
Interfejsy mogą dziedziczyć po jednym lub kilku interfejsach. Elementami implementującymi interfejsy są klasy lub struktury (mogą on implementować wiele interfejsów). W języku C# mamy również możliwość tworzenia generycznych interfejsów.
Niejawna implementacja interfejsu
Niejawna implementacja interfejsu jest najbardziej znaną formą implementacji. Polega ona na zaimplementowaniu publicznych elementów których definicje zawiera interfejs:
[csharp]
interface ISampleInterface1
{
void Print();
string Message { get; }
}
interface ISampleInterface2
{
void Print();
}
class SampleClass : ISampleInterface1, ISampleInterface2
{
// Zarówno ISampleInterface1.Print i ISampleInterface2.Print
// wywołają tą metodę
public void Print()
{
Console.WriteLine("Metoda Print");
}
private string _message = "";
public string Message
{
get { return _message; }
set { _message = value; }
}
}
[/csharp]
Jak widzimy nie ma tu żadnej magi implementujemy w klasie SampleClass metodę Print i właściwość Message z interfejsu ISampleInterface1 do której dopisujemy setter.
Jawna implementacja interfejsu
Aby jawnie zaimplementować interfejs musimy nazwę implementowanego elementu poprzedzić nazwą interfejsu:
Dzięki jawnej implementacji mamy możliwość zaimplementowani dwóch metod o tej samej nazwie co czasami może być niezmiernie przydatne. Przy jawnej implementacji trzeba pamiętać, że nie możemy używać modyfikatorów dostępu. Z jawną implementacją wiąże się też kilka niedogodności: implementacje elementów interfejsu nie są widoczne w klasie implementujące, aby uzyskać do nich dostęp musimy posługiwać się konkretny interfejsem. W przypadku właściwości Message nie możemy dopisać settera gdyż dostaniemy błąd kompilacji.
Przekazywanie parametrów mimo wszechobecności w każdej nawet najprostrzej aplikacji jest często pomijanym elementem w czasie nauki języka. Jest to na tyle prosta i naturalna czynność, że nie każdy zastanawia się jak to na prawdę funkcjonuje. Często słyszy się powszechnie, że: typy wartościowe przekazywane są przez wartość a obiekty przez referencję. Większości tak informacja wystarcza i nie zastanawiają się czy to w ogóle prawda i o co w tym tak na prawdę chodzi. Warto jednak przyjrzeć się temu tematowi choćby z tego powodu, że na rozmowach czy testach kwalifikacyjnych jest to powtarzający się element przy analizie kodu źródłowego. Zatem taka wiedza może okazać się niezbędna o ile zależy wam na dostaniu pracy o którą się ubiegamy.
W języku C# możemy przekazywać zmienne na dwa sposoby poprzez wartość i referencję. Przez wartość przekazujemy parametry podając jedynie ich nazwę jeżeli jednak chcemy aby funkcja miała możliwość dokonywania zmian na przekazywanych parametrach musimy przekazać je przez referencję wykorzystując do tego słowo kluczowe ref lub out. Różnica między tymi słówkami zostanie wyjaśniona w dalszej części.
Przekazywanie parametrów typu wartościowego
Typy wartościowe są najprostszymi dostępnymi typami, przechowują one dane bezpośrednio w przeciwieństwie do typów referencyjnych. Przekazywanie zmiennych typu wartościowego do metody jest równoznaczne z przekazaniem kopi tej zmiennej do metody. Oznacza to tyle, że w metodzie do której przekazaliśmy zmienne posługujemy się kopiami przekazanych zmiennych i zmiany dokonywane na nich nie mają wpływu na zmiany zmiennych które do metody przekazaliśmy.
[csharp]class PassingValByVal
{
static void SquareIt(int x)
{
x *= x;
Console.WriteLine("The value inside the method: {0}", x);
}
public static void Main()
{
int myInt = 5;
Console.WriteLine("The value before calling the method: {0}",
myInt);
SquareIt(myInt); // Passing myInt by value.
Console.WriteLine("The value after calling the method: {0}",
myInt);
}
} [/csharp]
Wynikiem powyższej aplikacji będzie:
The value before calling the method: 5
The value inside the method: 25
The value after calling the method: 5
Przekazując zmienną myInt do metody SquereIt kopiujemy jej zawartość do parametru x. Efektem czego wszystkie zmiany dokonywane są na kopi zmiennej myInt i nie mają żadnego wpływu na tą zmienną. Dlatego zmienna myInt przed jak i po wywołaniu metody SquareIt nie zmieniła swojej wartości.
Jeżeli chcemy dać możliwość metodzie bezpośredniego operowania na zmiennych przekazywanych do niej powinniśmy wykorzystać słowo kluczowe ref lub out.:
[csharp]class PassingValByRef
{
static void SquareIt(ref int x)
{
x *= x;
Console.WriteLine("The value inside the method: {0}", x);
}
public static void Main()
{
int myInt = 5;
Console.WriteLine("The value before calling the method: {0}",
myInt);
SquareIt(ref myInt); // Passing myInt by reference.
Console.WriteLine("The value after calling the method: {0}",
myInt);
}
}[/csharp]
Wynikiem powyższej aplikacji będzie:
The value before calling the method: 5
The value inside the method: 25
The value after calling the method: 25
Jak widzimy po wywołaniu metody SquareIt wartość zmiennej myInt uległa zmianie i uzyskaliśmy efekt o jaki nam chodziło podnieśliśmy do kwadratu zmienną którą przekazaliśmy do metody.
Przekazywanie parametrów typu referencyjnego
Typy referencyjne przechowują adres w pamięci określający gdzie obiekt się znajduje. Warto jednak pamiętać, że sama referencja zawierająca adres obiektu jest typem wartościowym którego wielkość zależy od platformy na jaką aplikacja została utworzona (system 32 lub 64 bitowy). Zatem przekazując zmienną typu referencyjnego przekazujemy ją również przez wartość z tą różnicą, że cały czas jest to wskaźnik na miejsce w pamięci dzięki czemu w metodzie możemy modyfikować obiekt na który przekazana referencja wskazuję. Jednak mimo możliwości zmian w obiekcie nie możemy dokonać zmian na samej referencji gdyż jest to jej kopia.
Powyższy kod po wykonaniu wyrzuci nam wyjątek NullReferenceException gdyż po wywołaniu metody Change zmienna builder nadal ma wartość null. Dzieje się tak ponieważ do metody przekazaliśmy kopię referencji wskazującej na obiekt klasy StringBuilder. Fakt operowania kopią referencji sprawił, że utworzony obiekt był przypisany do kopi referencji, a nie do zmiennej builder.
Aby móc dokonywać zmian nie tylko na obiekcie ale również na jego referencji musimy dodać słowo kluczowe ref lub out.
Append inside method.
Append after calling method.
Dzięki użyciu słówka ref mogliśmy utworzyć obiekt wewnątrz metody i dokonać na nim zmian.
Więcej o ref i out
W prezentowanych przykładach używaliśmy jedynie słówka ref przy przekazywaniu parametrów przez referencją. Jak wspominałem wcześniej istnieje również możliwość użycia słowa kluczowego out które również umożliwia przekazanie parametru przez referencję. Jednak jest pewna różnica w zastosowaniu tych słów kluczowych. Różnica ta polega na tym, że w metodzie w której przekazujemy parametry ze out musimy zainicjować zmienną którą przekazujemy z tym słowem kluczowym. Ze słówkiem ref sytuacja jest taka, że przed przekazaniem zmiennej do funkcji musimy zmienną najpierw zainicjować.
Musimy również pamiętać, że do metod używających słów kluczowych ref lub out, czyli przekazując zmienne przez referencję nie możemy przekazać właściwości klasy jak również indekserów w niej zdefiniowanych.
Opisywane słowa kluczowe umożliwiają nam również przeciążanie metod dzięki czemu taki zapis jest poprawny:
[csharp]class MyClass
{
public void MyMethod(int i) {i = 10;}
public void MyMethod(out int i) {i = 10;}
}[/csharp]
Słowa kluczowe ref i out możemy użyć zamiennie jednak nie możemy ich użyć razem, dlatego poniższy kod jest niepoprawny:
[csharp]class MyClass
{
public void MyMethod(out int i) {i = 10;}
public void MyMethod(ref int i) {i = 10;}
}[/csharp]
Podsumowanie
Wracając do stwierdzenia przedstawionego na początku tego postu: typy wartościowe przekazywane są przez wartość a obiekty przez referencję nie trudno stwierdzić że jest to przesadne uogólnienie które nie jest prawdą. Typy wartościowe jak i typy referencyjne przekazujemy przez wartość mimo faktu że dzięki referencji możemy modyfikować obiekty na które ona wskazuje. Aby przekazać zmienne przez referencję musimy dodatkowo użyć jednego ze słów kluczowych ref i out które też mają swoją specyfikę i podlegają pewnym ograniczeniom o czym należy pamiętać. Kończąc chciałbym życzyć wszystkim udanych rozmów kwalifikacyjnych.
Zmienne typu wartościowego (ValueType) są najprostszymi strukturami danych dostępnymi w frameworku .NET. W odróżnieniu od typów referencyjnych ich wartość jest przechowywana na stosie co znacznie ułatwia i przyspiesza dostęp do danych. W przypadku typów referencyjnych na stosie jest przechowywana jedynie referencja na obiekt znajdujący się na stercie (heap).
Z uwagi na to, że w sieci jest dużo informacji na ten temat przedstawię tylko ciekawostki warte zapamiętania.
Typy wartościowe możemy podzielić na trzy główne kategorie:
Typy wbudowane
Typy wyliczeniowe (Enumeration)
Typy tworzone przez użytkownika (Struktury)
Wszystkie typy wartościowe są pochodnymi klasy System.ValueType. Główną cechą zmiennych tego typu jest fakt, że po przypisaniu jednej zmiennej do innej dostajemy kopię danego obiektu. Zatem po takiej operacji dostajemy dwie niezależne zmienne. Podobna sytuacja ma miejsce podczas przekazywania takich zmiennych jako parametry w funkcjach.
Domyślna implementacja metody ValueType.Equals do porównywania wartości wykorzystuje system refleksji. Nadpisanie tej metody w określonych typach może znacznie zwiększyć wydajność ich porównywania.
Typy wbudowane
Pierwszą ważną kwestią jest inicjalizacji zmiennych lokalnych, gdyż brak inicjalizacji powoduje błąd podczas kompilacji. Typy wartościowe będące polami klas nie muszą być inicjalizowany. Przy tworzeniu obiektu danej klasy mają przypisaną domyślną wartość dla danego typu.
Nazwy typów int, long, double itd są tylko aliasami na typy, odpowiednio System.Int32, System.Int64, System.Double. Wszystkie aliasy możemy zobaczyć tutaj.
Każdy typ wartościowy posiada domyślny konstruktor który możemy wywołać:
[csharp]int i = new int();
double d = new double();[/csharp]
Po wywołaniu konstruktora zostanie utworzony obiekt z domyślną wartością.
Enum
Kolejnym typem wartościowym jest typ wyliczeniowy. Domyślnym typem dla typu enumeracyjnego jest int, aby wskazać inny typ podajemy go po nazwie, do dyspozycji mamy wszystkie typy całkowite za wyjątkiem typu char
[csharp]enum Days : long { Mon=1, Tue, Wed, Thu, Fri, Sat, Sun };[/csharp]
Elementy typu wyliczeniowego możemy rzutować na typ w jakim wartości są przechowywane (w tym przypadku long) jak i możliwa jest konwersja w drugą stronę.
[csharp] long mon = (long) Days.Mon;
Days d = (Days) mon;[/csharp]
Domyślną wartością każdego typu enumeracyjnego jest 0, zatem warto o tym pamiętać przypisując wartości do poszczególnych jej elementów.
[csharp]Days days = new Days(); //Domyślny konstruktor
Days days = 0; //nie wymaga rzutowania
Days days = (Days)1; //rzutowanie wymagane
[/csharp]
Słowem kluczowym new możemy zastąpić dany typ enumeracyjny zagnieżdżony w klasie na nowy.
Flags
Aby móc typem enumeracyjnym posługiwać się jako flagami musimy dodać do niego atrybut [Flags]. Warto pamiętać aby do konkretnych elementów typu wyliczeniowego przypisać wartości będące kolejnymi potęgami 2, w przeciwnym wypadku typ może zachowywać się nie tak jak byśmy tego oczekiwali.
[csharp][Flags]
enum Colors : short
{
Black = 0,
Red = 1,
Green = 2,
Blue = 4,
All = Black | Red | Green | Blue
}
[/csharp]
Struct
Struktury są typem definiowanym przez użytkownika. Struktury posiadają kilka podstawowych cech o których warto pamiętać:
rozmiar struktury musi być nie większy niż 16 B (bajtów)
struktura nie może posiadać bezparametrowego konstruktora
w strukturach nie ma dziedziczenia tak jak w klasach jednakże struktura jest pochodną klasy System.Object
sturuktua nie może być bazą dla klasy jak również struktura nie może dziedziczyć po innej strukturze lub klasie
mimo braku dziedziczenia struktury mogą implementować interfejsy.
Dzięki atrybutom możemy określić niestandardowe rozłożenie struktury w pamięci (coś na wzór union w C/C++)
[csharp]
[StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{
[FieldOffset(0)]
public long lg;
[FieldOffset(0)]
public int i1;
[FieldOffset(4)]
public int i2;
} [/csharp]
W powyższym przypadku pole lg wskazuje na ten sam obszar pamięci co pola i1 i i2. Zatem zmiana pola lg zmienia wartości pól i1 i i2, jak również zmiana pól i1 i/lub i2 zmienia wartość pole lg.
Nullable
Domyślnie do typu wartościowego nie można przypisać wartości null. Jednak platforma .NET dostarcza nam typ generyczyn umożliwiający takie przypisanie, mowa tu o strukturze Nullable<T>.
[csharp]
Nullable<int> nint = null;
int? a = null; // skrócony zapis
[/csharp]
Boxing/Unboxing
Boxing i unboxing to mechanizmy o których istnieniu warto wiedzieć. Prostym przykładem boxing’u jest poniższy kod:
[csharp]Point p = new Point();
object obj = (object)p;
p.X = 5;
Console.WriteLine(p);
Console.WriteLine(obj);
[/csharp]
Po wykonaniu tego kodu na ekranie ujrzymy:
{X=5,Y=0}
{X=0,Y=0}
Jak widzimy po przypisaniu struktury typu Point do zmiennej typu object uzyskaliśmy dwa różne obiekty, a to za sprawą faktu, że przy przypisaniu struktury do zmiennej typu referencyjnego obiekt został skopiowany do sterty i uzyskaliśmy dwie kopie tego samego obiektu jedna na stosie, a drugą na stercie. W wyniku czego zmiana pierwszego nie wpływa na drugi obiekt.
Więcej informacji jak również przykładowe ilustracje przybliżające działanie mechanizmu boxingu można znaleźć w The C# Value Type and Boxing .
Nie każdy początkujący programista, który zaczyna swoją przygodę z platformą .NET jest świadomy działania mechanizmów zawartych w technologi którą poznaję. Dobrym przykładem może być tu tworzenie zapytań za pomocą LINQ i Deferred Execution (opuźnione wywołanie). No ale co to oznacza, a no mniej więcej tyle , że zapytania tworzone za pomocą LINQ nie są automatycznie wykonywane podczas ich tworzenia. Wykonanie zapytania jest odłożone w czasie. Aby to wszystko lepiej zrozumieć przejdźmy do przykładu:
var result = from n in numbers where n < 5 select n;
numbers[0] = -5;
foreach (var i in result)
{
Console.Write("{0},", i);
}
[/csharp]
Resultat powyższego kody to ciąg liczb: -5,4,1,3,2,0. Zatem jak widać po utworzeniu zapytania nie zostało ono wykonane. Wywołanie zapytania miało miejsce dopiero podczas użycia pętli foreach i element który zmieniliśmy został uwzględniony w końcowym zbiorze.
Skoro już wiemy, że wykonanie zapytania stworzonego za pomocą LINQ nie jest automatycznie wykonywane to pewnie chcieli byście wiedzieć co zrobić żeby od razu dostać dane które chcemy. Odpowiedź jest dość prosta, wystarczy po utworzeniu zapytania wywołać metodę która zwróci nam końcowy zbiór elementów np. ToList, ToArray.
var result = (from n in numbers where n < 5 select n).ToList();
numbers[0] = -5;
foreach (var i in result)
{
Console.Write("{0},", i);
}
Console.WriteLine();[/csharp]
Wykonanie już takiego kody zwróci nam następujące liczby: 4,1,3,2,0.
Z Deferred Execution w LINQ wiąże się też jedna użyteczna funkcjonalność, a mianowicie możliwość ponownego użycia stworzonego zapytania. Dzięki temu tworzymy zapytanie raz i wykonujemy je dowolną ilość razy na zmodyfikowanym zbiorze.
numbers[0] = 11;
Console.WriteLine("Result count (after data changes): {0}", result.Count());
Console.WriteLine("First run");
foreach (var i in result)
{
Console.Write("{0},", i);
}
Console.WriteLine();
for (int i = 0; i < numbers.Length; ++i)
{
numbers[i] = -numbers[i];
}
Console.WriteLine("Second run (after data changes)");
foreach (var i in result)
{
Console.Write("{0},", i);
}
Console.WriteLine();
[/csharp]
Rezultat wykonania powyższego kody będzie następujący:
Result count: 9
Result count (after data changes): 8
First run
4,1,3,8,6,7,2,0,
Second run (after data changes)
-11,-4,-1,-3,-9,-8,-6,-7,-2,0,
Świadomość istnienia mechanizmów takich jak Deferred Execution w LINQ jest bardzo ważna, gdyż taka wiedza uchroni nas przed błędami, których wykrycie staje się bardzo ciężkie. Tak więc podsumowując warto zagłębiać się w tajniki technologi aby świadomie używać tego co oferuje nam dana technologia.
Przykładowy program można pobrać klikając na poniższy link:[dm]3[/dm]
System refleksji dostępny w .NET daje programiście duże możliwości operowanie strukturami klas podczas działania programu. Pobieranie informacji o poszczególnych elementach klasy wiążę się zazwyczaj z wywołaniem odpowiedniej metody i podania nazwy elementu klasy o którym informację chcielibyśmy uzyskać. Taki sposób operowania wewnętrzną strukturą programu jest mało wygodny i może powodować błędy podczas działania aplikacji.
Pewnie nie raz spotkaliście się z poniższym zapisem np. pisząc aplikację w ASP.NET MVC czy korzystając z frameworku Fluent NHibernate do definiowania mapowań dla NHibernate’a.
Wielu z was pewnie zastanawiało się jak zastosować taki mechanizm w pisanej bibliotece czy aplikacji. Z uwagi na to, że jakiś czas temu pisałem bibliotekę wykorzystującą taki sposób wskazywania elementów klasy postanowiłem, że opiszę pokrótce jak go zastosować. Główną zaletą stosowania takiego zapisu jest fakt, że dostajemy już na etapie kompilacji weryfikację poprawności takiego wyrażenia i automatyczne podpowiadanie składni na etapie jego tworzenia.
Żeby więcej nie przynudzać przejdę od razu do zaprezentowanie przykładu zastosowania wspomnianego mechanizmu. Zacznijmy od dwóch prostych klas których elementy będziemy wybierać:
[csharp]public enum Gender { Male, Female };
public class Person
{
public long Id { get; private set; }
public string Name { get; set; }
public string LastName { get; set; }
public string Profession { get; set; }
public Gender Gender { get; set; }
public DateTime BirthDate { get; set; }
public Address Address { get; set; }
}
[/csharp]
[csharp]public class Address
{
public string City { get; set; }
public string Street { get; set; }
public int Home { get; set; }
}
[/csharp]
Jak widzimy są to klasy reprezentujące odpowiednio osobę jak i adres. Aby móc pobierać informację o elementach klas musimy mieć narzędzie które nam to umożliwi. Do tego celu stworzymy prostą klasę generyczną która zwróci nam odpowiednie obiekty zawierające informację które chcemy uzyskać.
[csharp]
public class Info<T> where T: class
{
public PropertyInfo GetProperty(Expression<Func<T,object>> expression)
{
return ReflectionHelper.GetMemberExpression(expression.Body).Member as PropertyInfo;
}
GetProperty – zwracająca obiekt informacyjny o właściwości wskazanej przez wyrażenie lambda
GetAllPropertiesFromExpression – metoda zwracająca wszystkie właściwości w całym wyrażeniu
Obie metody jako parametr przyjmują System.Linq.Expressions.Expression<Func<T, object>>. Fakt, że w wyrażeniu operujemy delegatem funkcji daje nam to możliwość zastosowania wyrażeń lambda do ich tworzenia dzięki czemu uzyskujemy prosty zapis i podpowiadanie składni przy ich pisaniu.
Aby pobrać interesujące nas informację nie korzystamy z całości wyrażenia, a jedynie z jego części po operatorze “=>”. Dostęp do tej części uzyskujemy poprzez właściwość Body. Obiekt klasy System.Linq.Expressions.Expression zwracany przez właściwość Body nie posiada elementów z których możemy bezpośrednio uzyskać informację o członkach klasy które nas interesują. System.Linq.Expressions.Expression jest bazową klasą wszystkich wyrażeń i musimy ją rzutować, w naszym przypadku na obiekty klasy MemberExpression. Metoda realizująca to zadanie znajduje się poniżej.
[csharp]
public static MemberExpression GetMemberExpression(Expression expression)
{
MemberExpression memberExpression = null;
if (expression.NodeType == ExpressionType.Convert)
{
UnaryExpression body = (UnaryExpression)expression;
memberExpression = body.Operand as MemberExpression;
}
else if (expression.NodeType == ExpressionType.MemberAccess)
{
memberExpression = expression as MemberExpression;
}
if (memberExpression == null)
{
throw new ArgumentException(
String.Format("Expression {0} is not MemberExpression", expression.NodeType));
}
return memberExpression;
}
[/csharp]
W metodzie tej zawarta jest również część odpowiedzialna za rzutowanie wyrażenia na UnaryExpression i dopiero po tym uzyskujemy z niego obiekt klasy MemberExpression. Taka operacja jest wymagana aby dodać obsługę obiektów ValueType. Bez zastosowania UnaryExpression nie moglibyśmy pobierać informacji o członkach klasy będących ValueType.
Metoda GetMemberExpression umożliwia pobranie tylko ostatniego elementu wyrażenia. Jeżeli byśmy chcieli pobrać wszystkie właściwości z wyrażenia składającego się z kilku elementów musimy pobierać te elementy pojedynczo zaczynając od ostatniego i cofając się do elementu będącego parametrem. Metodę realizująca do zadanie widzimy poniżej.
[csharp]
public static PropertyInfo[] GetPropertiesFromExpression(Expression expression)
{
List properties = new List();
MemberExpression memberExpression = null;
Expression ex = expression;
do
{
memberExpression = ReflectionHelper.GetMemberExpression(ex);
properties.Add((PropertyInfo)memberExpression.Member);
ex = memberExpression.Expression;
}
while (ex.NodeType != ExpressionType.Parameter);
Console.WriteLine();
Console.WriteLine("Get all properties from expression: x => x.Address.City.Length");
var properties = personInfo.GetAllPropertiesFromExpression(x => x.Address.City.Length);
foreach (var item in properties)
{
Console.WriteLine("Type: {0}, Property: {1}",
item.DeclaringType.Name, item.Name);
}
[/csharp]
Po wykonaniu powyższego kawałku kodu na ekranie powinniśmy zobaczyć następujący efekt:
Person class:
Get "Name" Property: x => x.Name
Type: Person, Property: Name
Get all properties from expression: x => x.Address.City.Length
Type: Person, Property: Address
Type: Address, Property: City
Type: String, Property: Length
Kod zawierający program testowy wraz z wszystkimi opisanymi klasami można pobrać tutaj:
[dm]2[/dm]
Tworzenie i parsowanie adresów URL wykorzystując narzędzia dostarczone przez platformę .NET.
Traktowanie adresu URL jako stringa w programach jest dość wygodne jeżeli służy on tylko do przechowania adresu danego zasobu sieciowego. Jeżeli jednak takie adresy musimy generować lub wyciągać z nich niektóre parametry używanie stringa staje się nie praktyczne i wymusza na nas pisanie dodatkowych narzędzi. Wszystko w tym było by ok jeżeli pominąć fakt że framework .NET dostarcza nam właśnie zestaw klas do całej gamy operacji na adresach sieciowych.
Główną klasą odpowiedzialną za przechowywania adresów sieciowych jest klasa Uri. Jak widać nazwa klasy wskazuje, że posługujemy się URI (Uniform Resource Identifier), czyli czymś nadrzędnym niż sam URL.
Zatem platforma .NET dostarcza nam narzędzie o szerszym zastosowaniu niż budowanie samych adresów URL. Aby móc korzystać z klasy Uri jak i innych wymienionych w tym wpisie musimy do naszego projektu dodać referencję do System.ServiceModel.
Dzięki klasie Uri oprócz przechowywania danego identyfikatora sieciowego mamy możliwość weryfikacji jego poprawności przy tworzeniu takiego obiektu, jak również dostęp do poszczególnych jego części. Za pomocą tej klasy możemy tworzyć zarówno adresy względne (relative) jak i bezwzględne (absolute).
[csharp autolinks=”false”]Uri relative = new Uri("/2010/08/12/budowanie-i-parsowanie-adresow-url-w-net", UriKind.Relative);
Uri absolute = new Uri("http://blog.pietowski.com/2010/08/12/budowanie-i-parsowanie-adresow-url-w-net", UriKind.Absolute);
[/csharp]
Warto zapamiętać fakt, że obiektów klasy Uri nie można modyfikować gdyż wszystkie właściowści tej klasy umożliwiają tylko pobranie danych. Jeżeli chcemy mieć możliwość modyfikacji obiektu po jego utworzeniu powinniśmy użyć do tego celu klasy UriBuilder.
Jednak najbardziej przydatną i użyteczną klasą wydaje się być UriTemplate gdyż umożliwia nam definiowanie szablonów naszego identyfikatora sieciowego. Dzięki czemu możemy np. tworzyć automatycznie wiele adresów URL z różnymi parametrami jak również pobierać wybrane parametry z gotowych już adresów.
Elementem niezbędnym do utworzenia obiektu klasy UriTemplate jest string zawierający szablon tworzonych URI, który przekazujemy do konstruktora tej klasy. Format takiego stringa może mieć różne postacie, na przykład:
Przy tworzeniu obiektu weryfikowana jest poprawność wprowadzonego formatu zatem nie musimy się martwić, że przez przypadek wprowadzimy błędne dane. Nazwy zmiennych zamiast których będą wstawiane odpowiednie wartości zapisujemy między nawiasami klamrowymi “{” “}”. Wstawienie odpowiednich wartości do adresu uzyskujemy przy pomocy metody BindByPosition(Uri, String[]) do której przekazujemy bazowy adres i wszystkie parametry w kolejności w jakiej występują w szablonie.
[csharp autolinks=”false”]UriTemplate template = new UriTemplate("/{year}/{mounth}/{day}/{title}");
Uri baseAddress = new Uri("http://blog.pietowski.com");
Uri positionalUri = template.BindByPosition(baseAddress, "2010", "08", "12","budowanie-i-parsowanie-adresow-url-w-net");
[/csharp]
Innym sposobem przekazania parametrów jest utworzenie kolekcji NameValueCollection. Do obiektu takiej kolekcji dodajemy pary: nazwę parametru i jego wartość. Wynikowy Uri uzyskujemy wykonując metodę BindByName(Uri, NameValueCollection)do której przekazujemy bazowy adres i stworzoną kolekcję parametrów, co obrazuje poniższy przykład.
[csharp autolinks=”false”]UriTemplate template = new UriTemplate("/{year}/{mounth}/{day}/{title}");
Uri baseAddress = new Uri("http://blog.pietowski.com");
Uri namedUri = template.BindByName(baseAddress, parameters);
[/csharp]
Szczególne znaczenie w szablonie identyfikatora URI ma gwiazdka “*”, która służy do zastępowania pozostałej część ścieżki adresu. Symbol gwiazdki musi wystąpić raz i musi znajdować się na końcu ścieżki tworzonego szablonu URI. Stosowanie takiego zapisu przydaje się najbardziej w czasie dopasowywania gotowych Uri do stworzonego szablonu, służy do tego metoda Match(Uri, Uri). Metoda ta zwraca obiekt klasy UriTemplateMatch, z którego możemy pobierać poszczególne parametry szablonu ze wskazanego obiektu klasy Uri. Jeżeli wskazany Uri nie pasuje do szablonu zostanie zwrócony null.
Właściwość WildcardPathSegments w klasie UriTemplateMatch umożliwia pobranie segmentów pozostałej ścieżki, którą w szablonie zastąpiliśmy “*”:
[csharp autolinks=”false”]UriTemplate template = new UriTemplate("/{year}/*?file_id={id}");
Uri baseAddress = new Uri("http://blog.pietowski.com");
Uri fullUri = new Uri("http://blog.pietowski.com/2010/08/12/budowanie-i-parsowanie-adresow-url-w-net?file_id=2");
// Match a URI to a template
UriTemplateMatch results = template.Match(baseAddress, fullUri);
if (results != null)
{
Console.WriteLine("WildcardPathSegments:");
foreach (string segment in results.WildcardPathSegments)
{
Console.WriteLine("{0}", segment);
}
}
[/csharp]
Klasa UriTemplateMatch posiada poza wspomnianą powyżej wiele różnych właściwości dzięki którym możemy pobierać poszczególne elementy ścieżki, nazwy i wartości parametrów jak i wiele innych. Poniższy przykład prezentuję zastosowanie kilku z nich:
[csharp autolinks=”false”]
UriTemplate template = new UriTemplate("/{year}/{mounth}/{day}/{title}?file_id={id}&lang={lang}");
Uri baseAddress = new Uri("http://blog.pietowski.com");
Uri fullUri = new Uri("http://blog.pietowski.com/2010/08/12/budowanie-i-parsowanie-adresow-url-w-net?file_id=2&lang=pl");
Teraz przed pisaniem wyrażeń regularnych czy budowaniem własnych parserów warto przemyśleć zastosowanie gotowego rozwiązania jakie dostarcza nam platforma .NET gdyż w większości przypadków może to być najlepsze wyjście, które znacznie ułatwi nam posługiwanie się adresami sieciowymi w naszej aplikacji.
Więcej informacji odnoście opisywanych klas można znaleźć na stronach MSDN.
Pewnie nie raz zastanawialiście się jak szybko i bez nadmiernego wysiłku wyciągnąć informacje z dokumentu HTML. Niedomknięty znacznik czy brak apostrofów to już standard w większości stron. XHTML po części rozwiązuje niektóre problemy, ale stron w pełni walidowanych też za dużo nie uświadczymy. Mimo podobieństw większość stron HTML nie możemy traktować jak dokumentów XML. Platforma .NET nie dostarcza nam narzędzi do parsowania dokumentów HTML, pozostaje nam wiec korzystanie z zewnętrznych bibliotek.
W niniejszym wpisie chciałbym przedstawić bibliotekę którą poznałem już jakiś czas temu: Html Agility Pack, która nie raz już ułatwiła mi pracę. Główną zaletą tej biblioteki jest możliwość poruszanie sie po dokumencie HTML jak po dokumencie XML. Do wybierania elementów dokumentu mozemy uzyci języka XPath lub korzystając z LINQ (od wersji 1.4.0). Sam proces używania i posługiwania się wspomnianą biblioteką jest dość prosty i pokrótce zaprezentują go poniżej.
Oczywiście pierwszą czynnością jaką musimy wykonać aby móc korzystać z tej biblioteki jest dodanie do projektu referencji do pliku HTMLAgilityPack.dll. Po tej czynnosci mozemy korzystac z elementow jakie dostarcza nam biblioteka.
Klasą reprezentującą nasz dokument HTML jest HtmlDocument. Obiekt tej klasy tworzymy korzystając z domyślnego konstruktora.
[csharp]
WebClient client = new WebClient();
string html = client.DownloadString("http://blog.pietowski.com");
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);
[/csharp]
Dokument HTML możemy wczytać korzystając z metody Load do której możemy przekazać strumień, obiekt klasy TextReader lub ścieżkę do pliku. Alternatywą jest użycie medody LoadHtml, którą wykorzystałem w powyższym przykładzie, wczytującej dokument bezpośrednio z obiektu klasy System.String. Przed wczytaniem dokumentu możemy ustawić odpowiednie opcje parsowania ustawiając odpowiednie wartości polom o nazwach w formacie: OptionsXXX.
W celu pobranie błędów parsowania korzystamy z dostępnej właściwości ParseErrors:
Główny węzeł dokumentu dostępny jest pod właściwością DocumentNode korzystając z tego obiektu możemy przeglądać kolejne węzły wczytanego dokumentu. W celu pobrania elementu na podstawie identyfikatora używamy metody GetElementbyId.
Jeśli chcemy wyszukać konkretne węzły w naszym dokumencie możemy skorzystać z LINQ:
[csharp]
IEnumerable<HtmlNode> links = from link in doc.DocumentNode.DescendantNodes()
where link.Name == "a" && link.Attributes["href"] != null
select link;