Posts tagged ‘C#’

Ściąga z modyfikatorów dostępu

2009-10-16 16:08

W prawie wszystkich językach obiektowych istnieją tak zwane specyfikatory dostępu, przykładem których jest choćby public czy private. Pozwalają one ustalać, jak szeroko dostępne są poszczególne składniki klas w stosunku do reszty kodu. Modyfikatory te występują m.in. w trzech najpopularniejszych, kompilowalnych językach obiektowych: C++, C# i Javie.

Problem polega na tym, że w każdym z nich działają one inaczej. Mało tego: każdy z tych języków posiada inny zbiór tego rodzaju modyfikatorów. Dla kogoś, komu zdarza się pisać regularnie w przynajmniej dwóch spośród nich, może to być przyczyną pomyłek i nieporozumień.
Dlatego też przygotowałem prostą ściągawkę w postaci tabelki, którą prezentuję poniżej. Symbol języka występujący w komórce oznacza tutaj, że dany modyfikator (z wiersza) powoduje widoczność składnika klasy w określonym miejscu (z kolumny).

Specyfikator Klasa Podklasa Pakiet Reszta
public C++ C# Java C++ C# Java C# Java C++ C# Java
protected C++ C# Java C++ C# Java Java
protected internal C# C# C#
internal C# Java C# Java
private C++ C# Java

Parę uwag gwoli ścisłości:

  • Znaczek C++ nie występuje w kolumnie Pakiet nawet dla modyfikatora public, jako że w tym języku w ogóle nie istnieje pojęcie pakietu.
  • Sam termin ‘pakiet’ jest poza tym właściwy dla Javy. W .NET (C#) jego odpowiednikiem jest assembly, jakby ktoś nie wiedział :)
  • Z kolei internal jest słowem specyficznym dla C#, a jego ekwiwalentem w Javie jest po prostu pominięcie modyfikatora w deklaracji (np. int x; zamiast internal int x;).
  • W końcu, protected internal jest modyfikatorem występującym wyłącznie w C#/.NET.
Tags: , , , , , ,
Author: Xion, posted under Programming » 10 comments

Metody rozszerzające w C#

2009-09-11 12:28

Wspomnę dzisiaj o dość dziwnej funkcji, która została dodana w wersji 3.0 języka C#. Polega ona na możliwości dodania nowych metod do istniejących klas bez zmiany ich definicji. Odbywa się to poprzez zdefiniowanie tych dodatkowych metod jako statycznych (w innych klasach) i użyciu specjalnej składni dla jej pierwszego parametru. Oto przykład:

  1. public static class StringExtensions
  2. {
  3.     // zwraca liczbę znaków w ciągu
  4.     public static int WordCount(this String str)
  5.         { return str.Split(new char[] { ' ' }).Length; }
  6. }

Jeśli ktoś co nieco wie o tym, jak od środka działają metody obiektów w C++, to pewnie przypomina sobie, że to co wewnątrz metody jest wskaźnikiem this w rzeczywistości przekazywane jest metodzie jako jej pierwszy parametr. Pewnie ten fakt posłużył twórcom C# jako inspiracja przy ustalaniu składni metod rozszerzających.
Jak to działa? Otóż bardzo prosto. Dzięki powyższej definicji standardowa klasa System.String została teraz wzbogacona o nową metodę WordCount, której możemy użyć tak samo, jak każdej innej metody tej klasy:

  1. string s = "Ala ma kota";
  2. int words = s.WordCount();

Powód, dla którego fakt istnienia podobnego feature‘a nazwałem dość dziwnym, powinien się w tej chwili stać nieco jaśniejszy. Po krótkim zastanowieniu można bowiem stwierdzić, że niemal identyczny efekt można osiągnąć dwoma innymi sposobami:

  • Po pierwsze, możemy zdefiniować klasę pochodną po tej, której chcemy dodać metodę. Albo wręcz zdefiniować sobie po prostu zwykłą funkcję przyjmującą jeszcze zwyklejszy parametr i mieć coś w stylu StringUtils.WordCount(s). Jak widać, co najmniej jeden z tych sposobów można zastosować do każdej klasy – także takiej, której definicją nie dysponujemy (co w gruncie rzeczy jest banalne; na takich “sztuczkach” polega przecież całe programowanie obiektowe ;-]).
  • Po drugie, możemy zwyczajne pójść do deklaracji klasy i żądaną metodę dodać. Naturalnie, musi to być nasza własna klasa, której kodem dysponujemy i który możemy zmieniać.

Cechą wspólną obu tych “obejść” jest to, iż to my chcemy mieć tę nową metodę i to my ją definiujemy – zapewne dlatego, że to my chcemy jej potem używać. A skoro tak, to mamy wolność w ustaleniu sposobu jej wywoływania, ergo: nie ma znaczenia, jak ją zdefiniujemy. Któryś z tych dwóch/trzech sposobów będzie więc wystarczający.

Czyżby więc wychodziło na to, że C# w końcu dorobił się feature‘a, który rzeczywiście niczemu nie służy i jest kompletnie bez sensu? :) Prawie… Jak to z większością nowości w C# 3.0, przyczyna wprowadzenia metod rozszerzających streszcza się w – za przeproszeniem – czterech literach: LINQ.
LINQ opiera się bowiem na rozszerzeniu interfejsów pojemników (np. IEnumerable) o dodatkowe metody służące wykonywaniu na nich quasi-SQL-owych zapytań – jak np. Select czy Where. One właśnie zostały zaimplementowane jako metody rozszerzające. A że ponadto trafiły one do przestrzeni nazw System.Linq, to stają się dostępne dopiero po zadeklarowaniu użycia tej właśnie przestrzeni.
Reasumując: twórcy .NET-a dodali dwie nowości (metody rozszerzające oraz LINQ) po to, żeby ta pierwsza pozwalała na nieużywanie tej drugiej. Genialne, czyż nie? ;D

Tags: ,
Author: Xion, posted under Programming » 6 comments

LINQ bywa przydatny

2009-07-09 19:35

Wśród feature‘ów wprowadzonych w wersji 3.5 frameworka .NET jest między innymi LINQ (Language INtegrated Query). Mechanizm ten umożliwia – w dużym skrócie rzecz jasna – konstruowanie zapytań odnoszących się do kolekcji obiektów (w zasadzie dowolnego rodzaju) przy pomocy operatorów znanych z relacyjnych baz danych, jak SELECT czy WHERE. Ponadto w .NET 3.5 język C# został też odpowiednio rozszerzony, aby zapytania ta składniowo mogły przypominać kwerendy podobne do tych występujących w różnych odmianach języka SQL.

“A po co to komu?”, można zapytać od razu i nie ukrywam, że moja reakcja na LINQ była podobna. W końcu kto by chciał zaśmiecać taki ładny język jak C# zupełnie nieprzydatnym nikomu SQL-em? ;] Poza tym mówimy tutaj o czymś trochę z innej bajki, bowiem języki zapytań różnią się od języków programowania tym, że są deklaratywne: pisanie w nich polega na określeniu, co chcemy otrzymać – nie zaś tego, co po kolei program ma robić. (Jak nietrudno się domyślić, ogranicza to trochę stosowalność takich języków). Czemu więc chce się te dwie rzeczy połączyć?…

Odpowiedź jest jak zwykle dość prosta: dla wygody. Za pomocą LINQ nie zrobi się niczego nowego, ale pewne operacje można wykonywać prościej. Czasami można sobie oszczędzić dodatkowych pętli i zmiennych lokalnych – jak chociażby w poniższym przykładzie, w którym chcemy pobrać wszystkie wartości zaznaczone na liście z polami wyboru (check box list) i coś z nimi zrobić:

  1. // wersja z pętlą
  2. List<int> selected = new List<int>();
  3. foreach (ListItem item in checkBoxList.Items)
  4.     if (item.Selected) selected.Add (Convert.ToInt32(item.Value));
  5. DoSomething (selected);
  6.  
  7. // wersja LINQ
  8. DoSomething (from ListItem item in checkBoxList.Items
  9.              where item.Selected
  10.              select Convert.ToInt32(item.Value));

Można się oczywiście spierać, która wersja jest czytelniejsza i którą jest łatwiej/szybciej napisać. To jednak jest w dużym stopniu kwestią subiektywną. Jak dla mnie możliwość uniknięcia kolejnej pętli, która udaje, że coś robi (a tak naprawdę tylko przerzuca dane z jednego miejsca w inne) jest całkowicie zadowalająca.
Przy bardziej skomplikowanych przypadkach miałbym aczkolwiek dużo większe możliwości. Jest oczywiście możliwe pisanie błyskotliwych kwerend przemiatających po cztery pojemniki naraz i używających takich operatorów jak group czy orderby, by zrobić w trzech linijkach zapytania to, co inaczej wymagałoby 20 wierszy kodu. Sęk w tym, że prawdopodobnie odczytanie znaczenia tych 20 wierszy byłoby wówczas znacząco łatwiejsze ;P

Tags: ,
Author: Xion, posted under Programming » 10 comments

Sortowanie dla zaawansowanych

2009-07-02 18:06

Gdy trzeba posortować jakąś kolekcję w sposób – nazwijmy to – standardowy, to zwykle nie ma z tym problemu. Chyba każdy język posiada coś na takie okazje: mamy funkcję qsort, std::sort (i std::list::sort), System.Array.Sort, java.util.Arrays.sort, i tak dalej. Zwykle wystarczy podać im tablicę lub kolekcję i już mamy ją posortowaną.
Istnieją jednak przynajmniej dwie sytuacje, gdzie taka standardowa procedura nie jest wystarczająca. Jest tak na przykład wtedy, gdy:

  • zwykły sposób porównywania obiektów nie jest tym, którego chcielibyśmy używać przy sortowaniu. Ów “zwykły sposób” jest wyznaczany najczęściej przez operatory relacyjne (<, >=, itp.), które nie muszą być zdefiniowane (np. jeśli sortujemy obiekty własnego typu złożonego) lub mogą być określone inaczej niż byśmy w danym przypadku sortowania chcieli.
  • życzymy sobie odwrotnego porządku sortowania, czyli np. malejącego zamiast domyślnego rosnącego

W powyższych przypadkach rozwiązanie sprowadza się zwykle do zdefiniowania własnego kryterium porównawczego obiektów, które sortujemy. Spotyka się tu najczęściej dwie formy takiego kryterium:

  • W C++ możemy zdefiniować predykat binarny (funkcję przyjmującą dwa argumenty i zwracającą wartość binarny), który określa dla obiektów tzw. ścisłe uporządkowanie słabe (strict weak ordering). Mówiąc w skrócie, funkcja ta powinna mieć identyczne właściwości jak zwykły operator mniejszości <, czyli zwracać true, jeśli pierwszy obiekt jest “mniejszy” niż drugi. Jak nietrudno zauważyć, taka informacja jest wystarczająca do jednoznacznego uporządkowania dwóch obiektów. W szczególności a == b zachodzi wtedy i tylko wtedy, gdy !(a < b) && !(b < a), zatem taki predykat może też służyć do sprawdzania, czy dwa obiekty są sobie równe.
    Domyślnym predykatem sortowania w C++ jest std::less, który działa identycznie jak operator <. std::greater pozwala natomiast na odwrócenie porządku sortowania.
  • Wynik Relacja
    < 0 x < y
    == 0 x == y
    > 0 x > y

    W C, C# i w Javie korzysta się z kolei z funkcji porządkujących, które przyjmują dwa obiekty i zwracają w wyniku liczbę całkowitą. Liczba ta określa jednoznacznie uporządkowanie tych dwóch obiektów: czy jeden jest mniejszy niż drugi, równy mu czy większy od niego - tak, jak to pokazano w tabelce obok.
    Interesującą cechą takiego sposobu porządkowania jest to, że tak naprawdę używanie jakichkolwiek porównań (<=, > itp.) często nie jest w nim potrzebne. Zazwyczaj bowiem ową funkcję porównującą można zaimplementować następująco:

    1. int compare(x, y) { return val(x) - val(y); }

    gdzie val(x) oznacza tutaj pewną wartość liczbową przyporządkowaną obiektowi x, określającą jego miejsce w danym porządku sortowania. Wartość ta powinna być tak dobrana, aby posortowanie wszystkich takich liczb rosnąco dało nam w wyniku pożądaną kolejność uporządkowania naszych obiektów. Jeśli więc, przykładowo, chcemy posortować ciągi znaków w C# tak, by najdłuższe znalazły się na początku (czyli malejąco względem długości), to val(x) oznaczać tu będzie -x.Length().

Uff, jak widać sortowanie czasami nie jest wcale taką prostą sprawą :) Cieszmy się jednak, że nawet w bardziej skomplikowanych sytuacjach do napisania pozostają nam jedynie proste predykaty porównujące, nie zaś... całe algorytmy ;]

Tags: , , ,
Author: Xion, posted under Programming » 6 comments

O klasie Convert

2009-06-17 18:49

Stawiając pierwsze kroki w programowaniu w C#/.NET, można odkryć kilka ciekawych właściwości, które nie zawsze występują w innych językach. Jednym z nich jest całkiem dobre rozwiązanie odwiecznego problemu w kodowaniu, czyli zamiany między różnymi typami danych: zwłaszcza do i z łańcucha znaków.
Przykładem jest chociażby metoda ToString, która zrobi nam napis z dowolnego obiektu. Są też metody w stylu int.Parse, które potrafią odczytać liczbę zapisaną jako tekst i w zgrabny sposób rozwiązują jeden z najpowszechniejszych kłopotów wszystkich początkujących programistów :) (O ile oczywiście pomyślą oni o tym, że typ bądź co bądź, podstawowy, może mieć metody tak, jak klasa). W końcu, jest też rzutowanie; może ono służyć do “przywrócenia” właściwego typu obiektu, o którym z jakichś powodów “zapomnieliśmy”, ale również do konwersji między typami podstawowymi.

W sumie więc mechanizmów tego rodzaju jest dość dużo. Co więcej, nietrudno też – zwłaszcza w rzeczywistych, a nie przykładowych kodach – natrafić na jeszcze jeden. Jest to mianowicie użycie statycznej klasy Convert, zawierającej kilka metod typu ToDouble lub ToBoolean. I wydaje się, że korzystanie właśnie z niej najbardziej się opłaca. Dlaczego?
Ano głównie ze względu na jej uniwersalność – można z niej korzystać właściwie do wszystkich konwersji wyliczonych wyżej, i nie tylko. Daje to kod czytelniejszy, w którym od razu widać, co chcieliśmy zrobić – zwłaszcza jeśli alternatywą jest pokrętne obejście w rodzaju pośredniej konwersji do stringa. Ponadto klasa ta bywa sprytniejsza niż podane wcześniej sposoby, radząc sobie chociażby z typami danych używanymi przez API dostępu do baz danych (np. SqlInt32) i przerabiając je na bardziej użyteczne wartości.

W końcu, nie da się też ukryć, że korzystanie z mniej znanej, ale za to jakże wyspecjalizowanej klasy do prostych czynności jest niewątpliwą oznaką dużego profesjonalizmu… czyż nie? ;] I to pewnie jest najważniejszy argument ‘za’ ;P

Tags:
Author: Xion, posted under Programming » 17 comments

Funkcje jako dane wejściowe

2009-05-29 15:56

Sporo algorytmów jako swoje parametry przyjmuje różnego typu funkcje, które potem są wykorzystywane w trakcie ich działania. Prostym przykładem są tu wszelkiego rodzaju sortowania czy wyszukiwania, umożliwiające często podanie własnego predykatu (funkcji zwracającej wartość logiczną). W bardziej skomplikowanej wersji może chodzić chociażby o algorytm genetyczny lub przeszukujący drzewo gry, który wykorzystuje do działania jakąś funkcję oceniającą (np. osobników w populacji).

Na takie okazje różne języki programowania oferują różne narzędzia, lecz w większości przypadków sprowadzają się one do jednego z poniższych:

  • Wskaźniki lub delegaty. Przekazywanie funkcji przez wskaźnik jest proste i naturalne, ale ma pewne ograniczenia. W C(++) na przykład nie da się zwykłym wskaźnikiem pokazać na metodę konkretnego obiektu; docelowo może to być tylko funkcja globalna lub statyczna. Ten problem nie występuje zwykle w przypadku delegatów, którymi często można pokazać właściwie na wszystko – również na anonimową funkcję zdefiniowaną ad hoc:
    1. // sortowanie listy w oparciu o własny predykat (C# 2.0 wzwyż)
    2. objects.Sort (delegate (object a, object b)
    3.     { return a.ToString().Length - b.ToString().Length; });

    Niektóre języki nie mają jednak ani wskaźników, ani delegatów – w nich zwykle stosuje się sposób następny.

  • Interfejsy i metody wirtualne. Ta technika polega na zdefiniowaniu interfejsu (lub klasy abstrakcyjnej) z metodą wirtualną, którą docelowo będzie wywoływać algorytm. Korzystający z niego programista implementuje po prostu wskazany interfejs (lub definiuje klasę pochodną) i nadpisuje wspomnianą metodę własną wersją. Ten sposób jest szczególnie popularny w Javie, gdzie wspomagają go inne mechanizmy językowe – jak choćby niestatyczność klas wewnętrznych lub możliwość ich definiowania inline – np. w wywołaniu funkcji.
    Zauważmy też, że implementacja naszej ‘funkcji’ w postaci klasy pozwala też na dodatkowe możliwości, jak na przykład posiadanie przez nią stanu (co aczkolwiek w wielu przypadkach, np. predykatów sortowania, jest wysoce niezalecane).
  • Funktory. To rozwiązanie jest specyficzne dla C++ i opiera się na dwóch występujących w tym języku feature‘ach: szablonach i przeciążaniu operatorów. Dzięki temu algorytm korzystający z “czegoś co przypomina funkcję” może wyglądać choćby tak:
    template < typename T, typename SearchFunc >
    const T find(const std::vector& v, SearchFunc pred)
    {
    for (int i = 0; i < v.size(); ++i) if (pred(v[i])) return v[i]; // znaleziono element spełniający predykat return T(); }[/cpp] Określenie "coś co przypomina funkcję" jest jak najbardziej na miejscu, gdyż tutaj predykatem może być cokolwiek, co da się jako funkcję potraktować - czyli wywołać operatorem (). Może więc to być zwykły wskaźnik, jakaś ładna zewnętrzna implementacja mechanizmu delegatów (np. FastDelegate) lub jakikolwiek obiekt z przeciążonym operatorem nawiasów (). Właśnie te ostatnie nazywa się zwykle funktorami, a jeśli ktoś nieco bardziej zagłębił się w bibliotekę STL, na pewno nie raz się z nimi spotkał.

Trzeba też powiedzieć, że właściwie istnieje też inny sposób: zamiast samej funkcji przekazywanie jej… nazwy. “I niby jak ją potem wywołać?”, można zapytać. Ano to już indywidualna kwestia każdego języka – w tym przypadku zwykle interpretowanego :)

Tags: , , , ,
Author: Xion, posted under Programming » 5 comments

C#: referencje i porównywanie

2008-11-17 14:54

W C# obiekty zwykle dostępne są poprzez referencje. Zatem zmienna typu T (jeśli jest on klasą) nie zawiera samego obiektu T, lecz tylko odwołanie do niego. Porównując dwie takie zmienne przy pomocy operatora == domyślnie sprawdzamy więc, czy pokazują one na ten sam obiekt. Podobnie jest też przy użyciu domyślnej wersji metody System.Object.Equals; robi ona dokładnie to samo, co wspomniany operator. Można mądrze powiedzieć, że oba mechanizmy sprawdzają relację identyczności obiektów.

Czasami jednak chodzi nam o coś innego: chcemy sprawdzić, czy dwa obiekty są równe, np. w sensie zawartości swoich pól. Taka równość może zachodzić także wtedy, gdy obiektyte zostały stworzone zupełnie niezależnie. Znów można mądrze stwierdzić, że chcąc dokonać takiego sprawdzenia, realizujemy dla obiektów semantykę wartości. Co wtedy zrobić?… Ano przeciążyć tudzież nadpisać wspomnianą metodę Equals i/lub operator ==.
I tu zaczynają się schody, bo wcale nie jest łatwo zrobić to poprawnie, a jeszcze trudniej jest zrobić to sensownie. Wszystko zależy od tego, czy nasza klasa ma realizować wyłącznie ową nieszczęsną semantykę wartości czy też czasami będziemy jednak sprawdzać, czy dwie referencje pokazują na ten sam obiekt (a nie na dwa równe obiekty).

  • Jeśli tak (tzn. tylko sprawdzanie równości obiektów), to powinniśmy przeciążyć zarówno operator ==, jak i metodę Equals – i to przeciążyć tak, aby działały tak samo. Wtedy zwykle zajmujemy się najpierw operatorem, a później wykorzystujemy go w metodzie:
    1. struct Foo
    2. {
    3.     private int field;
    4.  
    5.     public static bool operator == (Foo a, Foo b)    { a.field == b.field; }
    6.     public override bool Equals(Object obj)
    7.         { return obj is Foo && this == (Foo)obj; }
    8. }

    W takiej sytuacji prawdopodobnie powinniśmy też posłużyć się strukturą. Najpewniej chodzi nam bowiem o typ, który ma zachowywać się jak podstawowy: czyli coś w stylu wektora, macierzy, kwaternionu, itp.

  • W przeciwnym przypadku (czyli: czasem porównujemy wartości, a czasem referencje) klasa powinna pozostać klasą, lecz należy przedefiniować jej metodę Equals. To jej powinniśmy używać, do sprawdzenia relacji równości między obiektami. Kiedy zaś chcemy zwyczajnie porównać referencje, możemy wtedy uciec się do operatora ==.

Uff, całkiem to zawiłe, prawda? Niestety nie jest łatwo uchwycić różnicę między równością a identycznością – a w językach, które obiekty realizują przez referencję sytuacja komplikuje się jeszcze bardziej. Zaś już chyba całkiem rozmywa się wtedy, gdy niektóre typy traktujemy per wartość, a niektóre per referencja…
A tak przecież jest C#. I jednocześnie podobno to jeden z prostszych języków do nauki i użytkowania :D

Tags: , ,
Author: Xion, posted under Programming » 5 comments
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.