Język C# 2.0
Szybki kurs
Wojciech Jaworski
Język C#
• Najważniejszy język programowania dla platformy .NET
• Obiektowy, prosty, nowoczesny i bezpieczny
• Najbardziej wszechstronny, pozwala korzystać ze wszystkich możliwości
platformy
• Łatwy do nauki – bardzo duże podobieństwo do Javy
• Główny architekt – Anders Hejlsberg
• Obecna wersja 2.0 ukazała się w październiku 2005
• W przygotowaniu wersja 3.0
System operacyjny (Win 32)
Message
Queueing
COM+
IIS
WMI
Common Language Runtime (CLR)
.NET Framework Class Library
ADO.NET i XML
Web Forms
Windows Forms
Web Services
C#
VB.NET
J#
C++
PERL, Python, ...
Hello world!
using System;
class Hello
{
public static void Main()
{
Console.WriteLine("Hello world!");
}
}
Co na początek warto wiedzieć o Console
• Podstawowy strumień wejścia-wyjścia w aplikacjach konsolowych
• Obsługuje czytanie i pisane, przemieszczanie kursora po konsoli, zmianę
ustawień klawiatury a nawet ustawienie koloru tła i obsługę głośniczka
systemowego
• Pozwala na formatowanie wyprowadzanego tekstu:
Console.WriteLine(
„
Liczba {0:F2} z dwiema cyframi po
przecinku
”
, 12.34556");
• Formaty:
• C – format walutowy
• D – notacja dziesiętna, pozwala określić liczbę wyświetlanych cyfr
• E – notacja wykładnicza
• F – notacja zmiennoprzecinkowa z ustaloną liczbą miejsc po przecinku
• N – format liczbowy określony w Ustawieniach regionalnych
• X – format szesnastkowy
Program w C#
• Aplikacja w C# to zbiór klas, struktur, interfejsów i wyliczeń
• Podstawowe elementy programu to klasy, zasady ich budowy bardzo
podobne do C++ i Javy
• Punktem startowym aplikacji jest statyczna metoda Main()
• Klasa składa się z pól, metod i właściwości, oznakowanych
modyfikatorami dostępu znanymi z innych języków
• Aplikacja w C# może składać się z wielu plików
• Klasa w C# może być podzielona na wiele plików, jeden plik może
zawierać wiele klas
• Nazwa pliku nie musi być taka sama jak nazwa klasy
Przestrzenie nazw
• Klasy .NET Framework Class Library są pogrupowane w przestrzenie
nazw (namespaces)
• Namespace dla naszej aplikacji tworzymy umieszczając odpowiednie
klasy w blokach namespace Nazwa { … }
• Odwołania do klasy:
• z podaniem pełnej „ścieżki” w przestrzeni nazw:
System.Console.WriteLine();
• użycie using w nagłówku pliku:
using System;
………
Console.WriteLine();
Wbudowane typy danych
Podział typów danych
Typy bezpośrednie
• Wyliczenia i struktury
• Zmienne danego typu umieszczane na stosie
• Bezpośrednio zawierają swoje dane
• Każda ma własną kopię swoich danych
• Operacje na jednej nie mają wpływu na drugą
Typy referencyjne
• Obiekty umieszczane na stercie
• Zmienne zawierają referencje do swoich danych (do obiektu)
• Dwie zmienne mogą odnosić się do tego samego obiektu
• Operacje na jednej zmiennej mogą mieć wpływ na drugą
Typy C# a typy CLS
Typ C#
Rozmiar
Typ CLS
bool
1
System.Boolean
byte
1
System.Byte
sbyte
1
System.SByte
short
2
System.Int16
ushort
2
System.UInt16
char
2
System.Char
int
4
System.Int32
uint
4
System.UInt32
float
4
System.Single
double
8
System.Double
long
8
System.Int64
ulong
8
System.UInt64
decimal
16
System.Decimal
object
4
System.Object
string
4
System.String
Uwagi na temat typów bezpośrednich
• Zmienne tworzymy w standardowy sposób:
int a = 18;
• Przed pierwszym użyciem zmiennej trzeba jej przypisać wartość (nie
dotyczy to pól klasy, którym są automatycznie inicjowane)
• Deklaracje stałych poprzedzamy słowem kluczowym const,
readonly – zmienna, której tylko raz można przypisać wartość
• Konwersje do liczbowych typów „pojemniejszych” dokonują się
automatycznie, w pozostałych przypadkach należy użyć operatora
rzutowania
int i =10; short s =(int)x;
• Brak niejawnej konwersji do typu logicznego jak w C
int i = 1; if (i) {…} //BŁĄD
if (i = 2) {…}
//BŁĄD
Klasa Object
• W C# wszystko (a nie tylko prawie wszystko) jest obiektem
• Niektóre metody klasy Object:
Equals() GetType() ToString() MemberwiseClone()
• Wszystkie typy (w tym typy bezpośrednie) wywodzą się z Object,
posiadają ponadto wiele własnych metod, poprawne są zatem konstrukcje:
int x = int.ParseInt(”12”);
25.ToString();
• Istnieje mechanizm automatycznego opakowywania wartości skalarnych
do obiektów
int x =14; object o = x;
//OK
Operatory uporządkowane względem priorytetu
( )
grupowanie lub wywołania metody
. ->
operatory dostępu do składowych
[]
operator indeksowania
++ --
operatory inkrementacji i dekrementacji
new
tworzenie nowego obiektu w pamięci
typeof, sizeof
typ obiektu, rozmiar
! ~
negacja logiczna ( ! ) lub bitowa ( ~ )
*
operator dereferencji
&
operator pobrania adresu
(typ)
operator rzutowania typu
* / %
operatory mnożenia, dzielenia oraz modulo
+ -
operatory dodawania i odejmowania
>> <<
przesunięcie bitowe w prawo lub w lewo
> < >= <=
operatory relacji
is
operator zgodności typów
as
operator rzutowania w dół hierarchii klas
== !=
operatory równości i nierówności
| & ^
operatory logiczne bitowe.
|| &&
operatory logicznej alternatywy oraz koniunkcji
?:
trójargumentowy operator warunku
= *= += -= %= &= ... operatory przypisania
Uwagi do operatorów
• Gdy operatory mają ten sam priorytet o ich kolejności decyduje łączność
• Operatory przypisań oraz jednoargumentowe łączą od prawej strony,
natomiast operatory dwuargumentowe (z wyjątkiem przypisań) od lewej
• Ponadto istnieją operatory checked i unchecked służące do sprawdzania
przepełnienia, np.
int a = checked( b * c ) ;
Instrukcje warunkowe
if ( Boolean-expression )
first-statement
else
second-statement
switch ( expression )
{
case value1:
instr; break;
case value2:
instr; break;
…
default:
instr; break;
}
Pętle
while (Boolean-expression )
{ blok instrukcji }
do
{ blok instrukcji }
while (Boolean-expression )
for (int i=0;i<10;i++)
{ blok instrukcji }
ArrayList<int> numbers = new ArrayList <int>();
foreach (int number in numbers)
Console.WriteLine(number);
Instrukcje skoku
• goto – skok do wybranego miejsca oznaczonego etykietą
• break – wyjście z pętli
• continue – natychmiastowe rozpoczęcie kolejnego kroku pętli
• return – zwrócenie wyniku przez metodę i jej zakończenie
Preprocesor
• Dyrektywy w C# podobnie jak w C pozwalają na preprocessing
• W porównaniu do C preprocesor C# ma mniejsze możliwości, nie
obsługuje makr
• Zestaw dyrektyw obejmuje
#define #undef #if #else #elif #endif
#pragma #warning #error #line #region
• Dyrektywa #region pozwala narzędziom takim jak Visual Studio na
ukrywanie kodu w edytorze
Wyliczenia
• Bardzo proste typy definiujące zbiór możliwych wartości
• Wartości są synonimami dla wartości całkowitych
• Definicja:
enum Color { Red, Green, Blue };
• Użycie:
Color myBest = Color.Green;
Tworzenie klasy
class BankAccount
{
private decimal _balance;
//pola klasy
private string _name;
public BankAccount(string name)
//konstruktor
{ _name = name; }
public void Withdraw(decimal amount)
//metody
{ ... }
public void Deposit(decimal amount)
{ ... }
public string Name {
//właściwości
get { return _name; }
set { _name = value; } }
}
Modyfikatory dostępu
• public : składowa lub typ zadeklarowany jako publiczny są dostępne z
dowolnego miejsca. Ten rodzaj dostępu jest domyślny dla interfejsów
• private : składowa zadeklarowana jako prywatna jest dostępna tylko z
wnętrza typu, w którym została zadeklarowana. Ten rodzaj dostępu jest
domyślny dla składowych klas i struktur
• protected : składowa zadeklarowana jako chroniona jest dostępna z
wnętrza klasy, w której została zadeklarowana lub z wnętrza klasy
pochodnej
• internal : typ lub składowa typu są dostępne tylko z wnętrza pakietu, w
którym nastąpiła ich deklaracja
• protected internal : składowa zadeklarowana z takim rodzajem dostępu
jest widoczna z wnętrza klasy, w której została zadeklarowana (lub klasy
pochodnej od niej) oraz z wnętrza pakietu, w którym się znajduje
Metody
• Metody mogą być statyczne tj. wykonywane na rzecz klasy, w C# w
przeciwieństwie do Javy nie można wywołać statycznej metody na obiekcie
• Klasa może posiadać przeciążone metody – o tej samej nazwie, różniące
się sygnaturą (na sygnaturę nie mają wpływu nazwy parametrów a tylko ich
typy, ani typ zwracany przez procedurę)
• Argumenty procedury mogą być przekazywane przez wartość, przez
referencję lub jako parametry wyjściowe:
public static void DoSomething(string param1,
ref string param2, out string param3);
Właściwości (properties)
• Popularny sposób enkapsulacji w C#, właściwości zapewniają podobny
dostęp jak pola
• get dostęp do odczytu
• set dostęp do zapisu
• Właściwości to „logiczne pola” – wartość zwracana przez accessor get
może być obliczana
• Properties to nie wartości; nie mają adresu
• Properties nie mogą być użyte jako parametry ref lub out metod
• Semantycznie są podobne do metod, mogą być virtual, abstract lub
override, ale nie mogą być typu void ani posiadać parametrów
Tworzenie obiektu
• Każda klasa posiada metodę tworzącą nowy egzemplarz klasy –
konstruktor. Nazwa konstruktora jest taka sama jak nazwa klasy.
• Klasa może posiadać wiele konstruktorów różniących się typem i liczbą
parametrów. Brak konstruktora w definicji klasy powoduje utworzenie przez
kompilator domyślnego bezparametrowego konstruktora
• Jeżeli napiszesz swój własny konstruktor, kompilator nie stworzy
konstruktora domyślnego
• Tworzenie obiektu – operator new zwraca referencję do obiektu
utworzonego przez konstruktor
BankAccount yours = new BankAccount(” Kowalski” );
Zmienne referencyjne
• Do obiektów odwołujemy się zawsze przez referencje
• Wiele referencji może wskazywać na ten sam obiekt, po utracie ostatniej
referencji obiekt zostanie po pewnym czasie skasowany przez GC
• W przypadku zmiennych referencyjnych operatory działają na
referencjach a nie wartościach obiektów
• Aby porównać zawartość obiektów zamiast == należy użyć metody
Equals()
• Tworzenie płytkiej kopii – metoda MemberwiseClone()
• Gdy w skład naszego obiektu wchodzą inne zmienne referencyjne dla
prawidłowego działania tych i innych metod klasy Object, musimy je
najczęściej w naszej klasie przedefiniować
Operator as
• Działa dla typów referencyjnych jak rzutowanie
• W przypadku błędu
• zwraca null
• nie wyrzuca wyjątku
Bird b = a as Bird; // Convert
if (b == null)
Console.WriteLine("Not a bird");
Operator is
• Zwraca true, jeżeli można wykonać konwersję
Bird b;
if (a is Bird)
b = (Bird) a; // Safe
else
Console.WriteLine("Not a Bird");
Operator this
• this odwołuje się do obiektu, na którym wywołano metodę
class BankAccount
{
...
public void SetName(string name)
{
this._name = name;
}
private string _name;
}
Statyczne klasy i konstruktory
• Oznaczenie klasy jako statyczną zabrania tworzenia obiektów tej klasy
• Klasy statyczne nie mogą zawierać niestatycznych składników ani
konstruktora
• Konstruktor statyczny może zostać umieszczony w dowolnej klasie,
zostanie wywołany przed wywołaniem właściwego konstruktora
• Konstruktor statyczny nie może posiadać modyfikatorów dostępu
Destruktory
• Destruktor to metoda, która zostanie wywołana tuż przed usunięciem
obiektu z pamięci
• Sam destruktor nie zajmuje się zwalnianiem pamięci, ale może np.
zamknąć otwarte pliki
• O momencie wywołania destruktora decyduje Garbage Collector
• Definicja destruktora:
~NazwaKlasy { … }
• Brak modyfikatorów dostępu
Wcześniejsze zwalnianie zasobów
• Możemy zażądać wcześniejszego zwolnienia zasobów implementując
interfejs IDisposable – umieścić w klasie definicję metody Dispose()
• Należy wtedy wyłączyć mechanizm automatycznego odzyskiwania
pamięci metodą GC.SuppressFinalize(this) w ciele metody Dispose()
• Instrukcja using – automatyczne wywołanie Dispose() po bloku instrukcji
using ( Font f = new Font(„Arial”) )
{
…
}
//tu wykona się f.Dispose()
Dziedziczenie w C#
• Składnia:
class KlasaOgolniejsza
{
...
}
class KlasaSzegolowa: KlasaOgolniejsza
{
...
}
• Klasa dziedzicząca nie może być bardziej dostępna niż podstawowa
• Brak wielodziedziczenia, rozwiązanie: interfejsy
Dziedziczenie a konstruktory
• W deklaracji konstruktora z parametrami musi być użyte słowo base
class Token
{
protected Token(string name) { ... }
...
}
class CommentToken: Token
{
public CommentToken(string name) : base(name) { }
...
}
Metody wirtualne
• Metody wirtualne są polimorficzne
• Składnia:
class Token
{ ...
public virtual string Name( ) { ... }
}
class CommentToken: Token
{ ...
public override string Name( ) { ... }
}
Zasady nadpisywania
• Trzeba dopasować metodę nadpisaną do jej metody wirtualnej
• Można nadpisać nadpisaną metodę
• Nie wolno deklarować metody override jako virtual
• Nie wolno deklarować metody override jako static lub private
class Token
{ ...
public int LineNumber( ) { ... }
public virtual string Name( ) { ... }
}
class CommentToken: Token
{ ...
public override int LineNumber( ) { ... }
//BLAD
public override string Name( ) { ... }
//OK
private override string Name( ) { ... }
//BLAD
public override string Name(int k) { ... }
//BLAD
}
new do ukrywania metod
• Do unikania kolizji nazw
• Składnia:
class Token
{ ...
public int LineNumber( ) { ... }
}
class CommentToken: Token
{ ...
new public int LineNumber( ) { ... }
}
Zagadka
• Co wyświetli program?
class A {
public virtual void M() { Console.Write("A"); }
}
class B: A {
public override void M() { Console.Write("B"); }
}
class C: B {
new public virtual void M() { Console.Write("C"); }
}
class D: C {
public override void M() { Console.Write("D"); }
static void Main() {
D d = new D(); C c = d; B b = c; A a = b;
d.M(); c.M(); b.M(); a.M();
}
}
Metody abstrakcyjne
• Sygnatura metody poprzedzona słowem abstract
• Metody abstrakcyjne nie posiadają ciała
• Tylko klasy abstrakcyjne mogą deklarować abstrakcyjne metody
• Metody abstrakcyjne są wirtualne
• Metody nadpisane mogą nadpisywać metody abstrakcyjne na kolejnych
poziomach dziedziczenia
Klasy abstrakcyjne i zamknięte
• Jeżeli klasę oznaczymy modyfikatorem abstract nie będzie możliwe
tworzenie obiektów tej klasy
• Klasy abstrakcyjne mogą zawierać metod zwykłe lub abstrakcyjne
• Klasy zamknięte (sealed) uniemożliwiają dziedziczenia
• Wiele klas .NET Framework jest sealed: String, StringBuilder itd.
Interfejsy
• Interfejs to „klasa”, która zawiera tylko deklaracje metod
• Metody nie posiadają modyfikatorów dostępu, nie mają ciała
• Klasa może implementować wiele interfejsów
• Konwencja: nazwa rozpoczyna się od ‘I’
• Przykład:
interface IToken
{
int LineNumber( );
string Name( );
}
class Token: IToken
{
int LineNumber( ) { cialo_metody }
... }
Klasy abstrakcyjne a interfejsy
Podobieństwa
• Nie mogą mieć instancji
• Nie mogą być sealed
Różnice
• Interfejsy nie mogą zawierać implementacji
• Interfejsy nie mogą mieć składowych niepublicznych
• Interfejsy nie mogą rozszerzać nie-interfejsów
Struktury
• Typy bardzo podobne do klas, mogą zawierać pola, metody, właściwości,
konstruktory, tworzone za pomocą new
• Zmienne tych typów są bezpośrednie a nie referencyjne, zalecany w
przypadku małych typów
• Nie umożliwiają dziedziczenia, nie mogą zawierać bezparametrowych
konstruktorów (jeśli konstruktora brak nastąpi inicjalizacja wszystkich pól
wartościami domyślnymi)
struct Point {
int x, y;
public Point(x, y) {
this.x=x;
this.y=y;
}
}
Obsługa wyjątków
• Znana konstrukcja „try … catch … finally”
try
{
Console.WriteLine(„Podaj pierwszą liczbę");
int i = int.Parse(Console.ReadLine());
Console.WriteLine(„Podaj drugą liczbę");
int j = int.Parse(Console.ReadLine());
int k = i / j;
}
catch (OverflowException caught)
{Console.WriteLine(caught.Message);}
catch (DivideByZeroException caught.Message)
{Console.WriteLine(caught.Message);}
catch (Exception caught.Message)
{Console.WriteLine(caught.Message);}
finally
{
Console.WriteLine(„Ten kod wykona się zawsze”);
Console.ReadLine();
}
Wyrzucanie wyjątków
• Użytkownik może wyrzucić wyjątek za pomocą instrukcji:
• throw new <klasa wyjątku>
•throw new OutOfMemoryException();
• Użytkownik może zdefiniować swój własny wyjątek. Klasa wyjątku musi
dziedziczyć w sposób bezpośredni lub pośredni z klasy System.Exception
• Brak słowa kluczowego throws do umieszczenia w sygnaturze metody
Tablice
• Każdy typ tablicowy jest typem referencyjnym wywodzącym się z
System.Array
• Tworzenie prostych tablic
int[] tablInt = new int[ 100 ] ;
int[ ] tabl = { 1, 2 , 3 } ;
• Tworzenie tablic typów referencyjnych wymaga inicjalizacji wartości tablicy
• Możliwe jest tworzenie tablic wielowymiarowych oraz nieregularnych
int [,] tabl3D = new int[5,10]; //Regularna tablica
int [][] tabl2D = new int[10][] ; //Tablica nieregularna
Metody dostępne dla tablic
• Rank – zwraca ilość wymiarów tablicy
• Length – zwraca ilość pól tablicy
• GetLenght() – zwraca liczbę pól danego wymiaru
• Sort() – sortuje tablice
• BinarySearch() – szybkie wyszukiwanie w posortowanych tablicach
• Clear() – ustawia wartości elementów tablicy na zero lub null
• Clone() – klonuje daną tablice
• IndexOf() – zwraca indeks pierwszego wystąpienia danej wartości w
tablicy
Tablice argumentów
• Słowo kluczowe params pozwala przekazać do metody dowolną liczbę
argumentów bez konieczności jawnego tworzenia tablicy
public void DisplayParams(params int[] vals)
{
foreach (int i in vals)
Console.WriteLine(i);
}
...
DisplayParams(1,2,3,4,5);
Mechanizm indeksowania
• Pozwala na dostęp do obiektu tak, jakby był on tablicą
• Przypuśćmy, że stworzyliśmy graficzną kontrolkę MyComboBox,
przechowującą tablice stringów:
class MyComboBox
{
private string[] strings;
...
public string this[int index]
{
get { return strings[index]; }
// OBSLUZYC
set { strings[index] = value; } }
// WYJATKI!
}
...
MyComboBox box = new MyComboBox();
...
string s = box[2];
Kolekcje
• C# i .NET Framework dostarczają wielu standardowych kolekcji:
• List<T>
• Queue<T>
• Stack<T>
• Dictonary<T,K>
• typ T kolekcji może być dowolnym typem referencyjnym lub bezpośrednim
• po kolekcjach możemy w wygodny sposób iterować instrukcją foreach
lub skorzystać z enumeratorów
Interfejsy kolekcji
• Możemy tworzyć własne typy kolekcji implementując w naszej klasie
interfejsy kolekcji:
ICollection<T> IEnumerable<T> IComparable<T>
IList<T> IDictonary<T>
class IterowanaKlasa :IEnumerable<int>
{
private int[] tab = new int[10];
public IEnumerator<int> GetEnumerator()
{
foreach (int i in tab)
yield return i;
}
}
...
IterowanaKlasa klasa = new IterowanaKlasa();
foreach (int i in klasa)
Console.WriteLine(i);
Łańcuchy znaków
• Łańcuchami nazywamy literały znakowe umieszczone w cudzysłowach
oraz obiekty klasy string
• W zapisie literałów możemy używać znanych z C sekwencji sterujących
• Znak ‘@’ przed łańcuchem powoduje traktowanie dosłowne wszystkich
znaków
• Obiekty klasy string są niezmienne
• Klasa string oferuje wiele użytecznych metod narzędziowych:
Compare() Concat() Join() CompareTo() Insert()
LastIndexOf() Remove() Split() Substring()
ToCharArray() ToLower() ToUpper() Trim()
Manipulowanie łańcuchami
• Operatory (np. +=) i metody manipulujące łańcuchami zwracają zawsze
nowy łańcuch, gdyż obiekty string są niezmienne
• Tworzenie obiektów jest czasochłonne, więc jeśli chcemy często
manipulować łańcuchem dobrze jest użyć klasy StringBuilder, oferującej
metody: Append(), AppendFormat(), Insert(), Remove(), Replace()
String ala = „Ala ma kota”;
StringBuilder builder = new StringBuilder();
foreach (string s in ala.Split(‘ ‘))
builder.AppendFormat(
”
Wyraz: {0}\n”, s);
Console.WriteLine(builder.ToString());
Wyrażenia regularne
• C# obsługuje wyrażenia regularne o składni regexp z Perla 5
• Podstawową klasą do obsługi wyrażeń regularnych jest Regex
• Prosty przykład:
String ciag = "gk4435g52hjvj4hv";
Regex reg = new Regex("[0-9]+");
MatchCollection maches = reg.Matches(ciag);
foreach (Match mach in maches)
Console.WriteLine(mach.ToString());
if (reg.IsMatch("15454621"))
Console.WriteLine("Goal");
A to jeszcze nie koniec
• Atrybuty
• Przeciążanie operatorów
• Nullable types
• Wątki i mechanizmy synchronizacji
• Domeny aplikacji
• Refleksja typów
• Delegaty i zdarzenia
• Wskaźniki i praca z kodem niezarządzanym