C# 3.0.pdf

(525 KB) Pobierz
45272955 UNPDF
Programowanie
C#
Marcin Kawalerowicz
C# 3.0
ma nic wspólnego z C# 3.0. Trzecia wersja
platformy .NET posiada standardowy (zgod-
ny z normą ECMA-334) kompilator C# 2.0 i właściwie
nie jest niczym innym jak .NET Framework upiększo-
ny zestawem „fundacyjnym” (WWF – Windows Work-
low Foundation , WCF – Windows Communication
Foundation i WPF – Windows Presentation Founda-
tion ). Nadciągająca wersja .NET Framework związa-
na z Visual Studio 9 Codename „Orcas” będzie no-
sić dumny numer 3.5 i zawierać wersję 3.0 kompila-
tora C#.
Nowości będzie sporo i praktycznie wszystkie
idą w tym samym kierunku. Budują podstawy po-
zwalające na sprawne wykorzystanie Language In-
tegrated Query czyli LINQ. To rozszerzenie .NET
Framework pozwoli na tworzenie zapytań do róż-
nych struktur danych wprost z poziomu języka pro-
gramowania (na początku oprócz C# 3.0 także VB
9.0). Zacznijmy jednak od początku. Przyjrzyjmy się
po kolei wszystkim nowościom kompilatora C# 3.0.
Zaczniemy od nowych sposobów na inicjalizację ty-
pów zmiennych i tablic. Przyjrzymy się typom anoni-
mowym, wyrażeniom lambda i poznamy metody roz-
szerzające, by w końcu dotrzeć do translacji zapytań
oraz drzew wyrażeń. Zatrzymamy się więc w miej-
scu gdzie C# 3.0 bezpośrednio łączy się z LINQ. Po-
staramy się jednak nie wchodzić zbyt głęboko na je-
go tereny, gdyż jest to temat na tyle szeroki, że za-
sługuje na osobny artykuł.
Mono i C# 3.0
W tej chwili trwają pracę nad integracją opensourcowej,
wieloplatformowej implementacji .NET Framework Mo-
no z językiem C# 3.0. Kompilator Mono C# zawiera w tej
chwili pełną implementację C# 1.0 i 2.0 oraz pewne ele-
menty 3.0. Pełna integracja planowana jest w wersji 2.2
zapowiadanej na ostatni kwartał 2007.
lekcje wraz z ich zawartością. Dodatkowo progra-
mista dostaje do ręki rozszerzenie znanych z C#
2.0 anonimowych metod w postaci równie anoni-
mowych typów. Najnowszy kompilator C# jest na
tyle inteligentny, że rozpoznaje dynamicznie typ
lokalnej zmiennej na podstawie wyrażenia je ini-
cjującego. Korzystanie z niejawnej inicjalizacji ty-
pów jest bardzo proste. Tam gdzie normalnie de-
klarowany był typ zmiennej należy wstawić słowo
var i od razu zainicjować wartość
var s = "test";
var i = 1;
Kompilator sam zorientuje się, że s to string , a i to
int . Podobnie można postępować z tablicami
var doubles = new double[] { 1.0, 2.0, 3.0 };
I bardziej zaawansowanymi typami
Kierunek dynamika
Wydaje się, że Anders Hejlsberg, główny twórca
C#, stara się polerować najnowszą wersję swoje-
go dziecka tak, by nadać mu połysk zbliżony do
rubinowego. Dzięki temu w C# widać coraz więcej
cech języków dynamicznych takich jak na przy-
kład Ruby. Nie mamy tu jednak w żadnym wypad-
ku rewolucji. C# pozostaje językiem o sztywnej
kontroli typów. Zmiany mogą być jednak postrze-
gane symptomatycznie. W najnowszej wersji C#
można niejawnie inicjować typy zmiennych i tablic,
dynamicznie tworzyć instancje obiektów oraz ko-
var list = new List<int>();
Nic nie stoi również na przeszkodzie, by i typ tablicy
deiniować w sposób niejawny. W takim przypadku
pozwalamy kompilatorowi na samodzielną decyzję w
tej sprawie na podstawie inicjalizujących zmiennych.
Na przykład tak:
var ints = new[] { 1, 2, 3, 4 };
var strings = new[] { "1", "2", "3", "4" };
Trzeba jednak uważać. Następujące operacje są nie-
dozwolone.
Autor pracuje jako główny programista .NET w mona-
chijskiej irmie Trygon Softwareberatung, gdzie to z
upodobaniem majstruje przy procesach wytwarzania
oprogramowania i z determinacją aspiruje do miana
„pragmatycznego programisty”. Prywatnie miłośnik do-
brego kina i zapalony wiosenny biegacz.
Kontakt z autorem: mkawalerowicz@poczta.onet.pl
var v1;
var v2 = null;
var v3 = { 1, 2, 3 };
i = "i"; //podczas gdy var i = 1;
Równie łatwo tworzy się instancje obiektów. W tym
przypadku można jednak pójść o krok dalej i inicjując
24
www.sdjournal.org Software Developer’s Journal 10/2007
U stalmy szczegóły: .NET Framework 3.0 nie
45272955.012.png
C# 3.0
Listing 1. Liczba zespolona
klasy Extensions pokazanej na Listingu 1 można zastosować
następujące operacje:
public class ComplexNumber {
private double re ;
public double Re {
get { return re ; }
set { re = value ; }
}
private double im ;
public double Im {
get { return im ; }
set { im = value ; }
}
}
double d1 = d.Squere();
double d2 = d.Power(d1);
Tak klasa deiniująca metody rozszerzające jak i same metody
muszą być statyczne. Pierwszy parametr metody zaopatrzony
jest w słowo this , po którym deiniowana jest klasę rozszerza-
na (w naszym przypadku System.Double ). Aby użyć metod roz-
szerzających wystarczy zaimportować przestrzeń nazw, w
której zostały one zdeiniowane (poprzez using ). Nic nie stoi
na przeszkodzie by jako parametrów użyć zmiennych typowa-
nych (z ang. generics ). Następującą metodę rozszerzającą
public static void DoNothing<P>(this P t) { }
obiekt wypełnić jego pola. W ten sposób klasę przedstawioną
na Listingu 1 można zainicjować tak:
będzie można użyć do dowolnego typu danych. Będzie trzeba
uważać by nie pogubić się w piekiełku przypominającym ma-
krodeinicje języka C.
var z = new ComplexNumber { Re = 1.1, Im = 2.2 };
Podobnie inicjować można bardziej złożone klasy. I tak prosty
zbiór dwóch liczb zespolonych przedstawiony na Listingu 2
można zainicjować tak:
Wyrażenia lambda
Najpierw była zwykła metoda. Posiadała ona nazwę, mogła
zwracać jakąś wartość, miała parametry o określonym typie, no
i zawierała ciąg instrukcji. Później przyszła metoda anonimowa i,
jak sama nazwa wskazuje, nie musiała już mieć nazwy. Deinio-
wało się ją poprzez słowo delegate . Od teraz będziemy mieli wy-
rażenia lambda, które wymagają jeszcze mniej. Żadnego dele-
gate , żadnych typów parametrów, właściwie nawet nie potrze-
ba return . Wiele funkcjonalnych języków programowania takich
jak Lisp, OCaml czy F# używa wyrażeń lambda do deiniowa-
nia funkcji. W C# wyrażenia te są funkcjonalnym rozszerzeniem
anonimowych metod. Oznacza to, że nie wnosząc większych in-
nowacji co do zasady działania anonimowych metod starają się
wprowadzić ułatwienia w ich zastosowaniu i zapisie. I tak zamiast
var p = new SimpleComplexNumberSet {
Z1 = { Im = 1.1, Re = 2.2 },
Z2 = { Im = 3.3, Re = 4.4 }
};
Nowe dynamiczne cechy języka C# pozwalają na zastoso-
wanie anonimowych typów. Mechanizm ten jest podobny do
znanych z wersji 2.0 anonimowych metod i pozwala na de-
iniowanie typów w locie (ang. in-line ). I tak następujące de-
klaracje
var person1 = new { LastName = "Kawalerowicz", FirstName =
"Marcin" };
var person2 = new { LastName = "Kawalerowicz", FirstName =
"Sylwia" };
delegate(int x)
{
return x + x;
}
utworzą dwa obiekty o tej samej strukturze. Obiekty te nie będą
miały nazwy, będą dziedziczyć bezpośrednio z System.Object
i posiadać serię właściwości zapewniających dostęp do pól
klasy ( get/set ). Stosując nowe cechy języka C# można podczas
inicjalizacji kolekcji wypełnić ją zawartością. Na przykład tak:
można po prostu zapisać
x => x + x
Listing 2. Zbiór dwóch liczb zespolonych
List<int> ib = new List<int> { 0, 1, 1, 2, 3, 5, 8, 13};
public class SimpleComplexNumberSet {
private ComplexNumber z1 ;
public ComplexNumber Z1 {
get { return z1 ; }
set { z1 = value ; }
}
private ComplexNumber z2 ;
public ComplexNumber Z2 {
get { return z2 ; }
set { z2 = value ; }
}
}
lub
var cn = new List<ComplexNumber> {
new ComplexNumber { Re = 1.1, Im = 2.2 },
new ComplexNumber { Re = 3.3, Im = 4.4 }
};
Rozszerzamy
Extension methods (metody rozszerzające) deklarowane
są tak jak metody statyczne ale mogą być wywoływane tak
jakby były „zwykłymi” metodami instancji. I tak po deklaracji
Software Developer’s Journal 10/2007
www.sdjournal.org
25
45272955.013.png 45272955.014.png 45272955.015.png 45272955.001.png
Programowanie
C#
Jak widać zapis w postaci wyrażenia lambda to nic in-
nego jak lista parametrów po której następują znaki =>
oraz deinicja działania. Działaniem może być wyrażenie
lub ciąg instrukcji. Parametry mogą być deklarowane nie-
jawnie i jeśli jest ich więcej niż jeden to muszą być za-
mknięte w nawiasach. Oto przykłady zastosowania wyra-
żeń lambda:
Listing 4. Deinicja dwóch wyrażeń lambda
Func < ComplexNumber , double > abs =
z => Math . Sqrt ( z . Re * z . Re + z . Im * z . Im );
Func < ComplexNumber , double > arg =
z => {
if ( abs ( z ) == 0.0 )
throw new ArgumentOutOfRangeException ( "Argument is
undeined" );
if ( z . Im > 0.0 ) return Math . Atan2 ( z . Re , z . Im );
else return - 1 * Math . Atan2 ( z . Re , z . Im );
} ;
(int x) => x + 1
(int x, int y) => { return x + y; }
W praktyce wyrażenia lambda można używać anonimowo lub
przypisując je delegatowi. Obie formy mogą być używane jako
parametry innych funkcji i wyrażeń. Nasz delegat Func będzie
mógł reprezentować każdą funkcję jednoparametrową zwra-
cającą jakąś wartość
tak zapisane wyrażenie lambda można w prosty sposób prze-
chowywać i przetwarzać. Równie proste jest przekształcenie
tak przechowywanych danych na postać wykonywalną
delegate R Func<P, R>(P param);
Można mu przypisać implementacje przedstawione na
Listingu 4, których użyć można w następujący sposób:
var f = data.Compile();
Console.WriteLine(f(1));
abs(new ComplexNumber { Re = 1.1, Im = 2.2 });
arg(new ComplexNumber { Re = 1.1, Im = 2.2 });
Wprowadzenie elementów programowania funkcjonalne-
go do najnowszej wersji C# przyjęte zostało przez nie-
których programistów z rezerwą. Dało się słyszeć głosy,
że mieszanie programowania obiektowego i funkcjonalne-
go jest niezdrowe. Tak czy siak, to co działo się od dawna
w kręgach akademickich przenika do „języków praktyczne-
go zastosowania” (przepraszam wszystkich programistów
OCaml piszących aplikacje biznesowe i mistrzów F# two-
rzących programy użytkowe). Całe zamieszanie ma jed-
Wyrażenia lambda mają jeszcze jedną bardzo przydatną
cechę. Mogą być one mianowicie przekształcane w struk-
tury danych zamiast w standardowy kod pośredni platfor-
my .NET (MSIL)
ExpressionFunc<ComplexNumber, double> abs =
z => Math.Sqrt(z.Re * z.Re + z.Im * z.Im);
Listing 5. Przykładowe zastosowanie zintegrowanego z
C# języka zapytań
Co to oznacza? Nie mniej nie więcej tyle, że zamiast wy-
konywalnego kodu możemy otrzymać tak zwane drzewo
wyrażeń (ang. expression tree ), które można sobie wy-
obrazić jak opis działania danego wyrażenia lambda. By
zapisać wyrażenie lambda w postaci drzewa wyrażeń
należy przypisać go do zmiennej typu Expression<T> w
ten sposób:
using System ;
using System . Collections . Generic ;
using System . Linq ;
using System . Diagnostics ;
namespace LinqTest
{
class Program
{
static void Main ( string [] args )
{
Process [] processes = Process . GetProcesses ();
Expression<Func<int, int>> data = i => i + 1;
Listing 3. Klasa dodająca dwie metody rozszerzające
public static class Extensions
{
public static double Squere ( this double d )
{
return d * d ;
}
var pNames =
from p in processes
where p . ProcessName . StartsWith ( "S" )
select p . ProcessName ;
public static double Power ( this double d , double y )
{
return Math . Pow ( d , y );
}
foreach ( string s in pNames )
{
Console . WriteLine ( s );
}
}
}
}
}
26
www.sdjournal.org Software Developer’s Journal 10/2007
45272955.002.png 45272955.003.png 45272955.004.png 45272955.005.png 45272955.006.png 45272955.007.png 45272955.008.png
 
C# 3.0
W Sieci
stosując go do dowolnego źródła danych. I tak na przykład
nasze zapytanie przetłumaczone zostanie na:
http://msdn2.microsoft.com/en-us/vcsharp/aa336745.aspx
http://www.mono-project.com/CSharp_Compiler – kompilator
Mono C#
http://msdn2.microsoft.com/en-us/vstudio/aa700830.aspx
Visual Studio 2008
var pNames2 = processes
.Where(p => p.ProcessName.StartsWith("S"))
.Select(p => p.ProcessName);
Wygląda znajomo? Tak, to metody rozszerzające z parametrami
w postaci wyrażeń lambda. Naturalnie nic nie stoi na przeszko-
dzie by próbować zaimplementować wzorzec query expression
pattern za pomocą delegatów zamiast wyrażeń lambda oraz me-
tod instancji, jako że są one wywoływane w identyczny sposób
jak metody rozszerzające.
nak drugie dno. Przecież z przytoczonych przykładów ja-
sno wynika, że „to wszystko dało się zrobić już wcześniej”.
Wyrażenia lambda i drzewa wyrażeń mają jednak kolosal-
ne znaczenie dla integracji z LINQ. Pozwala ona bowiem
na zapis, tłumaczenie i niezależne przetwarzanie zintegro-
wanych zapytań.
Podsumowanie
C# w wersji 3.0 ma być w założeniu językiem o wiele bardziej dy-
namicznym i ekspresywnym. Nie będzie już kłopotów z żonglo-
waniem kawałkami stringów w celu utworzenia zapytania SQL,
które i tak na końcu okazywało się błędne. Co więcej, sztywną
kontrolę typów i autouzupełnianie będzie można używać w zapy-
taniach do dowolnych struktur danych, a drzewa zapytań umożli-
wią niezależne tworzenie narzędzi obsługujących LINQ. Pewną
trudność może nastręczać dość zawiła składnia, ale nic nie stoi
na przeszkodzie by już teraz zacząć się z nią zaznajamiać. Mi-
crosoft na swoich stronach internetowych udostępnia wersję beta
najnowszego .NET Framework 3.0 zawierającą kompilator C# 3.0
oraz środowisko Visual Studio 2008 „Orcas”. Cóż więcej potrzeba
młodym i odważnym w kolejnej podróży „ku przygodzie”. n
Sedno sprawy
Tak, krok po kroku, doszliśmy do sedna innowacji w C#
3.0. Teraz pozostając w domenie C# i nie zagłębiając się
zbytnio w „technologię” LINQ, która jest tematem samym
w sobie, odkryjemy tajniki rządzące współpracą LINQ i
C#. Nie jest bowiem tajemnicą, że praktycznie wszelkie
przedstawione powyżej nowości wprowadzone zostały
po to, by język był w stanie wspierać LINQ. Dzięki zinte-
growanemu językowi zapytań (czyli z ang. Language IN-
tegrated Query ) możliwe jest pisanie podobnych do SQL
zapytań wprost w kodzie C#. Co za tym idzie możliwe jest
bezpośrednie korzystanie z IntelliSense oraz automatycz-
nego sprawdzania składni wewnątrz zapytań. Wykorzy-
stanie nowych koncepcji języka C# pozwoliło na unieza-
leżnienie LINQ od konkretnych źródeł danych. Praktycz-
nie rzecz biorąc, możliwe jest pisanie zapytań do dowol-
nych struktur. Mogą to być relacyjne bazy danych (DLI-
NQ), struktury hierarchiczne takie jak XML (XLINQ) czy
nawet kolekcji obiektów umieszczone w pamięci. Niech
za przykład posłuży nam Listing 5. Przedstawione w nim
zapytanie przeiltruje wszystkie działające procesy w sys-
temie i znajdzie te, których nazwy rozpoczynają się od
„S”. Czytelnicy znający SQL z pewnością zwrócą uwagę
na „dziwną” kolejność komend w zapytaniu. Na początku
from później where , a dopiero na końcu select . Co tu się
dzieje? Słowem kluczem jest IntelliSense , czyli system
uzupełniania kodu w VisualStudio (w innych systemach
zwany z ang. code completion ). Jeśli select byłby, tak jak
się to należy, na początku zapytania, system podpowia-
dania VisualStudio zostałby mocno upośledzony, gdyż
nie byłby w stanie „zgadnąć” co takiego moglibyśmy wy-
selekcjonować.
Zajrzyjmy pod maskę najnowszej wersji C#. Przedsta-
wione na Listingu 5 zapytanie zostanie przetłumaczone
(za pomocą nowej cechy języka C# zwanej translacją za-
pytań – query expression translation ) na „zwykły” kod C#,
na przykład na zbiór metod implementujących wzorzec qu-
ery expression pattern . Wzorzec ten proponuje zbiór me-
tod, na które mogą być tłumaczone zintegrowane zapyta-
nia. Są to między innymi Where , Select , Join , OrderBy czy
GroupBy . Metody te, dla zachowania elastyczności, zaopa-
trzone mogą być w typowane parametry. Nic nie stoi na
przeszkodzie by samemu zaimplementować ten wzorzec
R E K L A M A
Software Developer’s Journal 10/2007
45272955.009.png 45272955.010.png 45272955.011.png
 
Zgłoś jeśli naruszono regulamin