[C#] Rzutowanie - problemy ze zrozumieniem

0

Witam,
mam pytanie z podstaw w ramach rozjaśnienia mi pewnych rzeczy...

Instrument instr = new Gitara();
Gitara dziedziczy po Instrument.

  1. Dlaczego instr nie ma dostępu do właściwości klasy: Gitara? Tzn. ma, ale tylko do tych przeładowanych (override).
  2. Jak czytać taki zapis, coś na kształt: instr jest Instrumentem typu gitara?
  3. W czym ten zapis jest lepszy od: Gitara instr = new Gitara();? Czy w tym, że używając Instrument instr; mogę gdzieś dalej w kodzie zainicjować tą zmienną jakimś typem pochodnym Instrumentu, nie koniecznie Gitary? Czy może jeszcze czymś.
  4. Jak prawidłowo na różne sposoby powinienem używać rzutowania pomiędzy różnymi typami instrumentów?
0

Na przykład możesz zrobić klasę Muzyk, który będzie miał pole Instrument. Klasa Instrument będzie miała wirtualną metodę Graj i jak będziesz chciał, żeby zagrała Orkiestra to zrobisz foreach po Muzykach i zrobisz muzyk.instrument.Graj().

0
SkyLiNe napisał(a)

Witam,
mam pytanie z podstaw w ramach rozjaśnienia mi pewnych rzeczy...

Instrument instr = new Gitara();
Gitara dziedziczy po Instrument.

  1. Dlaczego instr nie ma dostępu do właściwości klasy: Gitara? Tzn. ma, ale tylko do tych przeładowanych (override).

Bo instr jest typu INSTRUMENT a nie GITARA

  1. Jak czytać taki zapis, coś na kształt: instr jest Instrumentem typu gitara?

nie mam pojęcia o co Ci tu chodzi

  1. W czym ten zapis jest lepszy od: Gitara instr = new Gitara();?

a jest lepszy??

Czy w tym, że używając Instrument instr; mogę gdzieś dalej w kodzie zainicjować tą zmienną jakimś typem pochodnym Instrumentu, nie koniecznie Gitary? Czy może jeszcze czymś.

czy to jest coś lepszego to sprawa dyskusyjna - ot dodatkowa możliwość

  1. Jak prawidłowo na różne sposoby powinienem używać rzutowania pomiędzy różnymi typami instrumentów?

nie ma nieprawidłowych sposobów - takie po prostu nie działają

A teraz wracając do tego jak to powinno być - klasa Instrument powinna mieć wirtualną (i ew. abstrakcyjną) metodę Graj, która była by przeładowywana przez wszystkie klasy z niej dziedziczące.

0

autor:
Załóżmy że robisz funkcję ileSięZmieści, która operuje na klasie Mebel i np liczy ile takich mebli zmieści się w pokoju. Później ktoś inny korzysta z tej funkcji, ale podaje jej klasę Stolik, która rozszerza klasę Mebel np o pole ilośćNóg. Pisząc funkcję ileSięZmieści nie wiedziałeś, że ktoś zrobi klasę Stolik, ani jakie będzie mieć pola, więc nie miałeś nawet jak się do nich dobrać. Ale nie jest ci to potrzebne do policzenia wyniku. Klasa Stolik zawiera wszystko co ci potrzeba, bo dziedziczy po klasie Mebel, a więc ma wszystkie informacje, których mógłbyś oczekiwać od klasy Mebel. Podając klasę Stolik do funkcji ileSięZmieści dokonujesz niejawnego rzutowania.

0

Podając klasę Stolik do funkcji ileSięZmieści dokonujesz niejawnego rzutowania.

Niejawne rzutowanie (implicit casting) nie ma nic wspólnego z tym co napisałeś. Tutaj nie ma miejsca żadne rzutowanie.

  1. Dlaczego instr nie ma dostępu do właściwości klasy: Gitara? Tzn. ma, ale tylko do tych przeładowanych (override).

Bo nie każdy instrument to Gitara.

  1. Jak czytać taki zapis, coś na kształt: instr jest Instrumentem typu gitara?

Należy to rozumieć : gitara jest instrumentem.

  1. W czym ten zapis jest lepszy od: Gitara instr = new Gitara();? Czy w tym, że używając Instrument instr; mogę gdzieś dalej w kodzie zainicjować tą zmienną jakimś typem pochodnym Instrumentu, nie koniecznie Gitary? Czy może jeszcze czymś.

możesz jaśniej ?:)

  1. Jak prawidłowo na różne sposoby powinienem używać rzutowania pomiędzy różnymi typami instrumentów?

Tutaj nie ma miejsca rzutowanie.

0

Dzięki wszystkim!

Misiekd napisał(a)
SkyLiNe napisał(a)
  1. Dlaczego instr nie ma dostępu do właściwości klasy: Gitara? Tzn. ma, ale tylko do tych przeładowanych (override).

Bo instr jest typu INSTRUMENT a nie GITARA

Oj, chyba jednak instr jest typu GITARA.

Deti napisał(a)
SkyLiNe napisał(a)
  1. W czym ten zapis jest lepszy od: Gitara instr = new Gitara();? Czy w tym, że używając Instrument instr; mogę gdzieś dalej w kodzie zainicjować tą zmienną jakimś typem pochodnym Instrumentu, nie koniecznie Gitary? Czy może jeszcze czymś.

możesz jaśniej ?:)

No dlaczego używamy:
Instrument instr = new Gitara();
a nie:
Gitara instr = new Gitara();

czuję tą różnicę, ale chciałbym dobitne wyjaśnienie :).

1

No dlaczego używamy:
Instrument instr = new Gitara();
a nie:
Gitara instr = new Gitara();

Bo w pewnym momencie możemy chcieć zmienić sobie ten instrument na inny, a przy rzutowaniu do interfejsu potrzeba będzie zmiana tylko w jednej linijce.

Ma to szczególne znaczenie jeżeli piszesz jakąś bibliotekę, gdyż zmiana implementacji interfejsu Instrument w twojej bibliotece nie wymusi ingerencji w kod aplikacji końcowej.

Rzutowanie na interfejs czy klasę bazową przydaje się też, gdy chcemy zwrócić instancję obiektu klasy niepublicznej.

1
SkyLiNe napisał(a)

Oj, chyba jednak instr jest typu GITARA.

Instrument instr
nosz k*** albo ja czytać nie umiem albo Ty

No dlaczego używamy:
Instrument instr = new Gitara();
a nie:
Gitara instr = new Gitara();
czuję tą różnicę, ale chciałbym dobitne wyjaśnienie :).

bo tak sobie napisałeś :> Nie wiem skąd wziąłeś stwierdzenia w tonie "dlaczego używamy" czy "jest lepszy". Nikt, nigdzie nie powiedział "masz robić Instrument ins = new Gitara() a nie Gitara ins = new Gitara() bo ........ (wstaw co chcesz)". To wynika z problemu, który rozwiązujesz!

Może przykład do Ciebie bardziej trafi
Zadanie: Napisz program, gdzie będzie można podać dowolną ilość figur typu okrąg, kwadrat, prostokąt, trójkąt równoboczny, wczytaj odpowiednie dane dla każdej z nich (np. dla koła promień, dla kwadratu bok a, dla prostokąta boki a i b, dla trójkąta podstawa i wysokość) a następnie wylistuj pole i obwód każdej z nich w kolejności wprowadzania.

Z dziedziczeniem robię tak

public abstract class Figura
{
public abstract double Pole();
public abstract double Obwod();
}

public class Okrag : Figura
{
private double promien = 0.0;

public Okrag(double promien)
{
this.promien = promien;
}

public override double Pole()
{
return pi * promien * promien;
}

public override double Obwod()
{
return 2 * pi * promien;
}
}

public class Prostokat : Figura
{
private double a = 0.0;
private double b = 0.0;

public Prostokat(double a, double b)
{
this.a = a;
this.b = b;
}

public override double Pole()
{
return a * b;
}

public override double Obwod()
{
return 2 * a + 2 * b;
}
}

public class Kwadrat : Prostokat
{
public Kwadrat(double a) : base(a, a) { }
}

public class Trojkat : Figura
{
private double a = 0.0;
private double h = 0.0;

public Trojkat(double a, double h)
{
this.a = a;
this.h = h;
}

public override double Pole()
{
return a * h / 2;
}

public override double Obwod()
{
return 3 * a;
}
}

//i teraz główna metoda programu, która tworzy listę figur, dodaje jakieś figury a potem wypisuje po kolei ich pola i obwody

public ...
{
private List<Figura> listaFigur = new List<Figura>();
//dodaje "losowo" różne figury z różnymi danymi
listaFigur.Add(new Kolo(12));
listaFigur.Add(new Kolo(1.2));
listaFigur.Add(new Kwadrat(9467));
listaFigur.Add(new Kolo(10.3));
listaFigur.Add(new Trojkat(12, 2));
listaFigur.Add(new Kolo(15));
listaFigur.Add(new Kwadrat(3.56));
listaFigur.Add(new Trojkat(2, 12));
listaFigur.Add(new Trojkat(22, 22));
listaFigur.Add(new Kolo(1));
listaFigur.Add(new Kolo(1.63));
listaFigur.Add(new Kwadrat(12));
listaFigur.Add(new Trojkat(1.3, 2));
listaFigur.Add(new Kwadrat(10));
listaFigur.Add(new Prostokat(12, 2));
listaFigur.Add(new Kolo(122));

//wypisanie danych
foreach (Figura f in listaFigur)
{
Console.WriteLine("Pole: {0} Obwód: {1}", f.Pole(), f.Obwod());
}
}


Chodzi o to, że zarówno samą listę jak i kawałek kodu wypisujący dane guzik obchodzi co za klasy (figury) są na tej liście. Ważne jest tylko, że każda z tych klas MUSI dziedziczyć po klasie Figura.

Bez dziedziczenia będzie dużo więcej kodu na listowanie, każdy typ figur musiałbyś trzymać na osobnej liście i pamiętać kolejność ich dodawania. Przy dziedziczeniu dodanie nowej figury sprowadza się do napisania jej klasy i tyle, bez dziedziczenia dochodzi kolejna lista dla nowej klasy.

Mam nadzieję, że załapałeś bo jak nie to mi pomysły się skończyły jak Ci to przedstawić. Żeby zrozumieć jak działa dziedziczenie musisz najpierw zrozumieć po co ono w ogóle jest
0
Misiekd napisał(a)
SkyLiNe napisał(a)

Oj, chyba jednak instr jest typu GITARA.

Instrument instr
nosz k*** albo ja czytać nie umiem albo Ty

Nigdzie wcześniej nie pisałem:

Instrument instr;

tylko: Instrument instr = new Gitara();

A więc niezaprzeczalnie instr jest typu Gitara.

@Misiekd, jak ma Ci się załączać taki agresor, za każdym razem, kiedy coś jest dla kogoś nie zrozumiałe, to szkoda Twoich nerwów (i moich), odpuść sobie (i mi) ;).
Ale suma, suma sumarum dzięki, bo przykład, fajny, chociaż wystarczyłoby mi jednak tłumaczenie p. Wibowita.

I jeszcze takie coś. Załóżmy chcę używać:
```csharp
Instrument instr = new Gitara();

z całą polimorficzną otoczką, jednak czy można jakoś przerzutować to tak, żebym mógł używać jakiejś metody strikte Gitary? Załóżmy, chcę użyć funkcji WymienStruny(); która występuje tylko w klasie Gitara, mając oczywiście do dyspozycji obiekt instr.

1

Twoje pytania wynikają tylko i wyłącznie z braku podstaw. To "Instrument instr = new Gitara();" oznacza tyle, że inst jest typu INSTRUMENT. Koniec, kropka i choćbyś nie wiadomo jak się starał dla całego świata (kompilatora) instr jest typu Instrument. A fakt, że zmienną instr zainicjowałeś typem Gitara to inna bajka. Zrozum jedno - dla kompilatora zmienna jest takiego typu jaki jej nadasz przy deklaracji i nie zmienia tego nic, nieważne co tak naprawdę ona przechowuje.

To jest dokładnie tak samo jak idziesz do kumpla i mówisz: "mam w pudełku instrument muzyczny". Kumpel "wie", że skoro to instrument to pewnie można na nim grać, i nic więcej. A czy Ty do tego pudełka włożyłeś gitarę, fortepian czy dudy to nie ma znaczenia.

Co do "przerzutowania" to nazywa się to rzutowanie (bez prze) i można to zrobić na dwa sposoby:

  • (instr as Gitara).WymienStruny(); - jeśli instr nie jest typu Gitara to wynikiem rzutowania będzie null i próba wywołania metody WymienStruny da w efekcie NullReferenceException
  • ((Gitara)instr).WymienStruny(); - jeśli instr nie jest typu Gitara to dostaniesz od razu wyjątek InvalidCastException
    Jest też jeszcze operator is, który sprawdza czy zmienna jest danego typu, np
    if (instr is Gitara)
    {
    ((Gitara)instr).WymienStruny();
    }
    else
    {
    Console.WriteLine("To nie jest gitarra!!");
    }
</ul>
0

Ponoć szybsze jest takie użycie as zamiast is:

Gitara g = instr as Gitara;
if(g != null)
    g.WymienStruny();
0

@Misiekd, ooo, dzięki za to rzutowanko.
Co do podstaw, to rzeczywiście, pewnie mi brak (jakoś bardzo tego nie ukrywam ;)), więc dlatego pytam.
W takim razie dlaczego:

Instrument instr = new Gitara();
MessageBox.Show(instr.GetType().ToString());

Zwraca: Gitara?

0

somekind:
W Scali jest ładniejsza konstrukcja i to bez nulla, który psuje obiektowość. Poza tym nie widzę powodu, aby as był szybszy od is. Deus mógłby się wypowiedzieć.

instr match {
  g: Gitara => g.wymienStruny()
}

Nulle należy w miarę możliwości omijać.
http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

Zamiast Nulla można zastosować wzorzec Null Object Pattern albo klasę typu Option ze Scali.

Co do podstaw, to rzeczywiście, pewnie mi brak (jakoś bardzo tego nie ukrywam ), więc dlatego pytam.
W takim razie dlaczego:

Instrument instr = new Gitara();
MessageBox.Show(instr.GetType().ToString());

Zwraca: Gitara?

Bo instr tutaj jest typu Gitara. W Javie pierwsze bajty obiektu w pamięci wskazują na klasę, której jest egzemplarzem. W CLR pewnie podobnie.

http://en.wikipedia.org/wiki/Virtual_method_table

Kompilator może skompilować kod dla obiektów typu Instrument który zadziała z obiektami typu Gitara jeśli poda się mu odpowiednią vtable.

0
SkyLiNe napisał(a)
Instrument instr = new Gitara();
MessageBox.Show(instr.GetType().ToString());

Zwraca: Gitara?
nie odpowiada się pytaniem na pytanie ale
to dlaczego nie działa:
instr.WymienStruny();
skoro cały czas twierdzisz, że instr to Gitara a nie Instrument?
BTW GetType zwraca typ instancji obiektu a nie to jakiego typu jest zmienna - pooglądaj sobie przykłady
Ja Ci tego nie potrafię udowodnić - masz swoją wersję i z tego co widzę nic tego nie zmieni. Albo przyjmiesz do wiadomości, że dla kompilatora instr jest Instrument a nie Gitara i Twoje życie będzie dużo łatwiejsze (przynajmniej w aspekcie programowania w c#) albo będziesz obstawiał przy swoim i nie ruszysz dalej

0

Nie chodzi o to kto ma jaką wersję, bo wersja słuszna jest tylko jedna i takiej też tutaj szukamy, bo potem nagromadzi się masa niejasności i będzie ice tea (lipton ;)). Trochę pokombinowałem i wychodzi, że rzeczywiście instr jako zmienna referencyjna jest typu Instrument, jednak obiekt, na który pokazuje, jest typu: Gitara i stąd GetType() zwraca: Gitara.

Dlaczego WymienStruny(); nie działa? Właśnie... Dlaczego trzeba bawić się w rzutowanie, as, etc. Nie mogli tego lepiej zrobić? Skoro

Instrument instr = new Gitara();

to przecież wiadomo, że chodzi nam o gitarę i chcemy jej metody.

1

1 - GetType() to metoda wirtualna, co oznacza że jest wywoływana wersja z konkretnej klasy dziedziczącej, a niekoniecznie tej klasy której jest obiekt. Na tym polega cała magia dziedziczenia i polimorfizmu że mamy metody wirtualne.

2 - Nie mogli tego lepiej zrobić? ...
Instrument instr = new Gitara();
to przecież wiadomo, że chodzi nam o gitarę i chcemy jej metody.
Naprawdę? Jak dla mnie wynika stąd że chodzi nam o instrument i chcemy jego metody. Jeśli chcesz użyć metod gitary, to zrób z tego po prossstu obiekt typu gitara.
Nie mogli tego lepiej zrobić - jak to sobie konkretnie wyobrażasz? Że z każdego obiektu typu Instrument można wywołać metody Gitary? Albo Tamburyna?

0

Dlaczego WymienStruny(); nie działa? Właśnie... Dlaczego trzeba bawić się w rzutowanie, as, etc. Nie mogli tego lepiej zrobić? Skoro
Instrument instr = new Gitara();
to przecież wiadomo, że chodzi nam o gitarę i chcemy jej metody.

A potem sobie tą linijkę zamieniamy na:
Instrument instr = new Perkusja();

I co wtedy?

Gdy piszesz, "Instrument instr" to dajesz do zrozumienia kompilatorowi, że chodzi ci tylko o metody z klasy/ interfejsu Instrument.

Używanie i rzutowanie na interfejsy jest bardzo ważne, pomaga spełnić zasadę SRP z zestawu zasad SOLID. Więcej info tutaj:
http://koziolekweb.pl/category/inzynieria-oprogramowania/solidne-programowanie/

0
MSM napisał(a)

1 - GetType() to metoda wirtualna, co oznacza że jest wywoływana wersja z konkretnej klasy dziedziczącej, a niekoniecznie tej klasy której jest obiekt. Na tym polega cała magia dziedziczenia i polimorfizmu że mamy metody wirtualne.

Tak, mechanizm GetType() rozumiem.

MSM napisał(a)

2 - Nie mogli tego lepiej zrobić? ...
Instrument instr = new Gitara();
to przecież wiadomo, że chodzi nam o gitarę i chcemy jej metody.
Naprawdę? Jak dla mnie wynika stąd że chodzi nam o instrument i chcemy jego metody. Jeśli chcesz użyć metod gitary, to zrób z tego po prossstu obiekt typu gitara.
Nie mogli tego lepiej zrobić - jak to sobie konkretnie wyobrażasz? Że z każdego obiektu typu Instrument można wywołać metody Gitary? Albo Tamburyna?

Hmmm... No wyobrażałbym to sobie tak, że skoro klasa abstrakcyjna Instrument ma metody wirtualne, których nadpisane metody są odnajdywane w klasie Gitara, to dlaczego instr nie mógłby widzieć również pozostałych metod. Przecież wiadomo na co w danym momencie instr pokazuje (oj, no właśnie, chyba, że nie zawsze wiadomo?).

1

chyba, że nie zawsze wiadomo? - oczywiście że nie zawsze wiadomo :>
void do(Instrument i)
{ i.Graj(); }

void a()
{ do(new Gitara()); }

void b()
{ do(new Fortepian()); }

Bez sensu w takim przypadku byłoby żeby w metodzie Do() można było używać metod klasy Gitara, bo doprowadziłoby to do problemów jeśli dostanie np. fortepian.

Jeśli chodzi o zapis:

void c()
{ Instrument a = new Gitara(); a.WymieńStruny(); }

to... Z teoretycznego punktu widzenia nie ma problemu żeby kompilator na to pozwalał, ale... po co? Musiałby cię oszukać, bo wtedy zmienna a i tak byłaby typu Gitara a nie Instrument.
Jeszcze inaczej - jeśli chcesz kupić gitarę to czy wstawisz gdzieś ogłoszenie szukam instrumentu?

0

skyline:
Klasa Gitara ma osobny vtable dla potrzeb rzutowania na Instrument i z tym vtablem jest skojarzona zmienna instr przy przypisaniu Instrument instr = new Gitara(). Aby dostać się do reszty metod trzeba wyłuskać klasę obiektu i sprawdzić jakie ma dostępne vtable. Ja to sobie tak wyobrażam. Deus zapewne wie lepiej jak to się odbywa.

Poza tym twój pomysł to już zahacza o duck typing z języków dynamicznie typowanych. C#, o ile nie zastosujesz słówka kluczowego dynamic, jest językiem statycznie typowanym.

0
MSM napisał(a)

to... Z teoretycznego punktu widzenia nie ma problemu żeby kompilator na to pozwalał, ale... po co? Musiałby cię oszukać, bo wtedy zmienna a i tak byłaby typu Gitara a nie Instrument.
Jeszcze inaczej - jeśli chcesz kupić gitarę to czy wstawisz gdzieś ogłoszenie szukam instrumentu?

No tak, zaczynam dostrzegać już zalety tego rozwiązania i problemy, których bez jego funkcjonowania nie dałoby się obejść, no i oczywiście fakt, że w ten sposób kompilator nas też chroni, no bo załóżmy, że w czasie wykonywania kodu mamy referencję do Gitary, a za chwilę mamy referencję do Fortepianu, nie zrobiliśmy instanceof i nagle okazuje się, że Fortepian nie ma ZmienStruny(); i Exception.
Dzięki!

Wibowit napisał(a)

Poza tym twój pomysł to już zahacza o duck typing z języków dynamicznie typowanych. C#, o ile nie zastosujesz słówka kluczowego dynamic, jest językiem statycznie typowanym.

Takie rozróżnienia na języki nie znałem, przyjrzę się im z ciekawości ;).
Dzięki!

0
Wibowit napisał(a)

somekind:
W Scali jest ładniejsza konstrukcja

Cieszę się Twoim szczęściem. :P

Poza tym nie widzę powodu, aby as był szybszy od is.

Chyba urwałem w pół zdania: as jest ponoć szybszy od is + rzutowania w celu uzyskania konkretnego obiektu, bo as rzutuje raz, a is + rzutowanie to dwa rzutowania. Ale nie pamiętam gdzie dokładnie to wyczytałem, więc raciczki za to uciąć nie dam.

Zamiast Nulla można zastosować wzorzec Null Object Pattern albo klasę typu Option ze Scali.

Można też zastosować zasadę KISS i nie komplikować wszystkiego nadmiernie. NullReferenceException są wkurzające, ale tego się raczej już nie uniknie (chyba, że przepisałoby się cały kod na świecie od nowa).

SkyLiNe napisał(a)

załóżmy, że w czasie wykonywania kodu mamy referencję do Gitary, a za chwilę mamy referencję do Fortepianu, nie zrobiliśmy instanceof i nagle okazuje się, że Fortepian nie ma ZmienStruny();

Akurat Fortepian powinien mieć ZmienStruny(). ;)

Ty byś chciał, żeby kompilator znał wszystkie metody wszystkich klas pochodnych po Instrument. Jak już sam zauważyłeś byłoby to nieużywalne dla Ciebie - bo sam byś nie wiedział, którą metodę na obiekcie możesz wykonać. Ale przede wszystkim jest to fizycznie niewykonalne - nawet Ty możesz nie znać wszystkich klas, które dziedziczą po Twojej klasie, więc skąd kompilator miałby je znać?

0

Można też zastosować zasadę KISS i nie komplikować wszystkiego nadmiernie. NullReferenceException są wkurzające, ale tego się raczej już nie uniknie (chyba, że przepisałoby się cały kod na świecie od nowa).

Ale w nowym kodzie już można stosować.

Co powoduje mniej komplikacji: wstawianie w całym kodzie co chwilę if(referencja == null) cośtam else cośinnego; czy też może stworzenie jednorazowo klasy NullObject i zamiast return null dawanie return NullObject?

Poza tym null nie ma typu, wszelkie instanceof (is w C#) na nim dadzą negatywny wynik. Idąc tym tropem po zapisie:
Instrument instr = null;
I pomysłach SkyLine, kompilator musiałby od razu zabronić używania jakichkolwiek metod czy pól zmiennej instr, bo nie ma ona żadnych :)

0
Wibowit napisał(a)

Co powoduje mniej komplikacji: wstawianie w całym kodzie co chwilę if(referencja == null) cośtam else cośinnego; czy też może stworzenie jednorazowo klasy NullObject i zamiast return null dawanie return NullObject?

NullObject ma IMHO zastosowanie w pewnych przypadkach. Ja nie przypominam sobie, bym kiedykolwiek czegoś takiego potrzebował, jeżeli już to bardzo rzadko.
Zwykły null niesie prostą i ważną informację - coś takiego nie istnieje, więc nie masz z tym nic dalej do roboty.

Poza tym dyskusja była o tym, jak optymalnie sprawdzić typ klasy dziedziczącej, nie o dobroci nulla. ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1