Programowanie w
środowiskach graficznych
Wykład 4 Język C# cd
Wyjątki
Służą do raportowania i obsługi sytuacji wyjątkowych (błędów, które można w jakiś sposób naprawić).
Instrukcje, które mogą się z jakichś przyczyn nie udać, jak np.
• pobieranie danych od użytkownika,
• niepewne rzutowanie,
• operacje wejścia/wyjścia,
• alokację dużej ilości pamięci obudowujemy blokiem try.
object o2 = null;
try {
int i2 = (int)o2; // Casting Error }
Hierarchia klas wyjątków w C#
Wyjątki – catch, finally, throw
Bloki catch przechwytują rzucony wyjątek i starają się go obsłużyć (lub zaraportować użytkownikowi);
Pierwszy catch pasujący typem obsługuje wyjątek;
Blok finally służy do końcowych porządków (np. zwolnienie zasobów);
throw: słowo kluczowe umożliwiające (niekiedy ponowne) zgłoszenie wyjątku.
catch (FileNotFoundException e) {
// FileNotFoundExceptions are handled here.
}
catch (IOException e) {
if (e.Source != null)
Console.WriteLine("IOException source: {0}", e.Source);
throw;
}
Wyjątki – catch
Dopuszczalne jest użycie samego catch
try { …
} catch { …
}
Wskazana jest odpowiednia kolejność catch
try {
… }
catch (Exception exception) { …
} catch (IOException ioexception)
{ // tutaj nigdy nie dojdziemy, bo IOException jest // specjalizacją Exception, więc poprzedni catch // wyłapie też każdy wyjątek klasy IOException
Odpowiedniki wskaźników do funkcji z C++
Delegaty
public delegate int KtoryMniejszy (object o1, object o2);
KtoryMniejszyPorownanie += new KtoryMniejszy (FunkcjaPorownawcza);
public int FunkcjaPorownawcza (object o1, object o2) { return (o1>o2);
}
Tab.sort(Porownanie);
Delegaty i zdarzenia
Delegaty są wykorzystywane do obsługi zdarzeń. Visual Studio automatycznie je deklaruje i wrzuca do pliku designera, np.
Form1.Designer.cs
delegate void EventHandler (object sender, EventArgs e);
button1.Click += new EventHandler (bSubmit_Click);
void bSubmit_Click(object sender, EventArgs e) { Button nadawca = sender as Button;
… }
Generyczność
(programowanie generyczne)
Generyczność pozwala klasom, strukturom, interfejsom, delegatom i metodom być
sparametryzowanym przez typ.
Mechanizm podobny do szablonów w C++.
Dlaczego generyczność?
Przykład: stos obiektów dowolnej klasy.
Kompilator „nie wie”, jakiej klasy będą obiekty na stosie
Wymaga więc jawnego rzutowania, by wykonać kod specyficzny dla rzeczywistej klasy obiektu.
public class Stack{object[] items;int count;
public void Push(object item) {...}
public object Pop() {...}}
Stack stack = new Stack(); stack.Push(new Customer ("Nowak"));
Customer c = (Customer) stack.Pop();
c.obsłużKlienta();
A teraz to samo generycznie: parametryzujemy stos typem T i tworzymy stos Customer-ów:
Rzutowanie może być niejawne, bo kompilator wie, że na stosie wszystkie obiekty muszą być klasy Customer.
public class Stack<T>
{ T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}
Stack<Customer> stack = new Stack<Customer>();stack.Push(new Customer(„Adam Słodowy”));
… Customer serviced = stack.Pop();
serviced.obsłużKlienta();
Generyczność – dwa typy
Tutaj używamy napisów (klasa string) do indeksowania obiektów klasy Customer (tzw. tablica asocjacyjna).
public class Dictionary<K,V>
{ public void Add(K key, V value) {...}
public V this[K key] {...}}
Dictionary<string,Customer> dict=new Dictionary<string,Customer>();
dict.Add("Pit", new Customer("Peter Hall"));
Customer c = dict["Pit"];
}
Generyczność - kompilacja
Kompiluje się do IL (instrukcje i metadane)
Proces tworzenia – generic type instantion
Po jednej instancji dla każdego
używanego w kodzie typu prostego
Jedna instancja dla typów referencyjnych
Generyczność – ograniczenia na parametry (1/4)
Kompilator sygnalizuje błędem, że nieznana mu klasa K prawdopodobnie nie obsługuje porównań (wywołań CompareTo()).
public class Dictionary<K,V>{
public void Add(K key, V value) {...
if (key.CompareTo(x) < 0) // błąd kompilacji {...}
} }
I rozwiązanie: jawne rzutowanie
Generyczność – ograniczenia na parametry (2/4)
public class Dictionary<K,V>{
public void Add(K key, V value) {
...
if (((IComparable)key).CompareTo(x) <0) {...}
...
} }
II rozwiązanie: ograniczenia na parametry generyczne
Generyczność – ograniczenia na parametry (3/4)
public class Dictionary<K, V> where K : IComparable {
public void Add(K key, V value){...
if (key.CompareTo(x) < 0) {...}...}
}
Ograniczenia mogą dotyczyć wielu interfejsów, jednej klasy, a także konstruktora new():
Generyczność – ograniczenia na parametry (4/4)
public class EntityTable<K, E>
where K : IComparable<K>, IPersistable where E : Entity, new()
{
public void Add(K key, E entity){...
if (key.CompareTo(x) < 0) {...}...}
}
Metody generyczne 1/2
Możemy parametryzować także metody:
void PushMultiple<T> (Stack<T> stack, params T[] values) {
foreach (T value in values) stack.Push(value);
} ...
Stack<int> stack = new Stack<int>();
PushMultiple<int>(stack, 1, 2, 3, 4);
Metody generyczne 2/2
type inferencing – wywołanie metody z odpowiednim typem generycznym na podstawie dedukcji parametrów:
tak jak dla klas możemy wprowadzać też ograniczenia dla metod:
Stack<int> stack = new Stack<int>();
PushMultiple(stack, 1, 2, 3, 4); //wywołanie dla int
public class MyClass{
public void SomeMethod<T>(T t) where T :IComparable<T>
{ ...
}
Typy generyczne i rzutowanie 1/3
Niejawnie możemy rzutować do object i do typów występujących w
ograniczeniach:
class MyClass<T> where T : BaseClass,ISomeInterface {
void SomeMethod(T t) {
ISomeInterface obj1 = t;
BaseClass obj2 = t;
object obj3 = t;
} }
Jawnie rzutować możemy tylko do interfejsów:
Typy generyczne i rzutowanie 2/3
class MyClass<T> {
void SomeMethod(T t) {
ISomeInterface obj1 = (ISomeInterface)t; // Compiles SomeClass obj2 = (SomeClass)t; // ERROR!
} }
Możemy ominąć to ograniczenie
używając zmiennej pomocniczej typu object:
używać operatorów is i as.
Typy generyczne i rzutowanie 3/3
class MyClass<T> {
void SomeMethod(T t) {
object temp = t;
SomeClass obj = (SomeClass)temp;
} }
}
Dziedziczenie i generics 1/2
Jeżeli klasa dziedziczy z klasy generycznej to musi być podany konkretny typ jako parametr:
public class BaseClass<T>{...}
public class SubClass : BaseClass<int>{...}
Chyba że klasa i podklasa są sparametryzowane tym samym parametrem:
public class SubClass<T> : BaseClass<T> {...}
Dziedziczenie i generics 2/2
Przy dziedziczeniu należy powtarzać ograniczenia w podklasach:
public class BaseClass<T> where T : ISomeInterface {...}
public class SubClass<T> : BaseClass<T> where T : ISomeInterface{...}
To samo dotyczy metod wirtualnych:
public class BaseClass{
public virtual void SomeMethod<T>(T t) where T : new() {...}
}
public class SubClass : BaseClass{
public override void SomeMethod<T>(T t) where T : new() {...}
} 24
Generics i delegaty
Tak jak klasy struktury i interfejsy możemy sparametryzować typem generycznym także delegaty:
public delegate void GenericDelegate<T>(T t);
public class MyClass{
public void SomeMethod(int number) {...}
}
MyClass obj = new MyClass();
GenericDelegate<int> del;
del = new GenericDelegate<int>(obj.SomeMethod);
del(3);
Kolekcje generyczne i niegeneryczne
System.Collections.Generics i
odpowiedniki System.Collections
Comparer<T> Comparer
Dictionary<K,T> HashTable
List<T> ArrayList
Queue<T> Queue
SortedDictionary<K,T> SortedList
Stack<T> Stack
Metody anonimowe 1/3
Standardowy handler zdarzenia w osobnej metod
class InputForm: Form { ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += new EventHandler(AddClick);
}
void AddClick(object sender, EventArgs e) { listBox.Items.Add(textBox.Text);
}
Metody anonimowe 2/3
Ten sam przykład z użyciem metody anonimowej:
Kod jest krótszy, ale nie możemy używać tego samego handlera dla wielu kontrolek
class InputForm: Form{
ListBox listBox;
TextBox textBox;
Button addButton;
public MyForm() {
listBox = new ListBox(...);
textBox = new TextBox(...);
addButton = new Button(...);
addButton.Click += delegate{listBox.Items.Add(textBox.Text);};
} }
Metody anonimowe 3/3
Możemy także używać metod anonimowych do wpisywania funkcji “in-line”
delegate double Function (double x);
class Test{
static double[] Apply(double[] a, Function f) { … }
static double[] MultiplyAllBy(double[] a, double factor) {
return Apply(a, delegate(double x) { return x*factor; });
}
static void Main() {
double[] a = {0.0, 0.5, 1.0};
double[] squares = Apply(a, delegate(double x) { return x*x; });
double[] doubles = MultiplyAllBy(a, 2.0);
Iteratory 1/3
Żeby można było użyć pętli foreach na
kolekcji, to kolekcja musi spełniać warunki:
Kolekcja musi implementować interfejs IEnumerable.
Kolekcja musi mieć bezparametrową
metodę GetEnumerator().
Iteratory 2/3
„Numeratory” są trudne do zaimplementowania.
Sprawę ułatwiają nam ITERATORY:
public class Stack<T>: IEnumerable<T>
{
T[] items;
int count;
public void Push(T data) {...}
public T Pop() {...}
public IEnumerator<T> GetEnumerator() {
for (int i = count – 1; i >= 0; --i) { yield return items[i];
} }
}
Mamy też do dyspozycji instrukcję yield break
Iteratory 3/3
Możemy także zdefiniować kilka iteratorów dla 1 kolekcji:
public class Stack<T>: IEnumerable<T>
{
…
public IEnumerator<T> GetEnumerator() { for (int i = count – 1; i >= 0; --i) {
yield return items[i];
} }
public IEnumerable<T> TopToBottom { get {
return this;
} }
public IEnumerable<T> BottomToTop { get {
for (int i = 0; i < count; i++) { yield return items[i];
}
}
Iteratory 3/3 cd..
Użycie:
class Test {
static void Main() {
Stack<int> stack = new Stack<int>();
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack.TopToBottom) Console.Write("{0} ", i);
foreach (int i in stack.BottomToTop) Console.Write("{0} ", i);
}
}
Klasy częściowe 1/2
Podział zawartości 1 klasy w więcej niż jednym pliku np:
plik1.cs
public partial class Customer {
private int id;
private string name;
private string address;
private List<Order> orders;
public Customer() { ...
}
}
Klasy częściowe 2/2
Plik2.cs
public partial class Customer {
public void SubmitOrder(Order order) { orders.Add(order);
}
public bool HasOutstandingOrders() { return orders.Count > 0;
}
}
Nullable type 1/3
Wprowadza dla wszystkich typów prostych dodatkową możliwą wartość null.
int? Jest to typ wbudowany + wartość null.
HasValue - właściwość, która zwraca nam
true or false jeżeli typ jest nullable lub nie.
Nullable type 2/3
Operacje arytmetyczne z null-em dają null
Operatory >,<,<=, >= zwracają false, jeżeli któryś z argumentów jest null.
Nowy operator ??
a??b – rezultatem jest a jeżeli a jest
not-null i b w przeciwnym przypadku.
Nullable type 3/3
Przykład:
int? x = GetNullableInt();
int? y = GetNullableInt();
int? z = x ?? y;
int i = z ?? -1;
Operator ten działa też dla typów referencyjnych:
string s = GetStringValue();
Console.WriteLine(s ?? "Unspecified");