Wiele przypadków w instrukcji switch


582

Czy istnieje sposób na przejrzenie wielu instrukcji case bez podania nazwy case value: powtarzania?

Wiem, że to działa:

switch (value)
{
   case 1:
   case 2:
   case 3:
      // Do some stuff
      break;
   case 4:
   case 5:
   case 6:
      // Do some different stuff
      break;
   default:
       // Default stuff
      break;
}

ale chciałbym zrobić coś takiego:

switch (value)
{
   case 1,2,3:
      // Do something
      break;
   case 4,5,6:
      // Do something
      break;
   default:
      // Do the Default
      break;
}

Czy mam na myśli tę składnię z innego języka, czy coś pomijam?


Czy istnieje powód, dla którego nie używasz tylko instrukcji JEŻELI (jeśli sprawdzasz zakres wartości int)?
Eric Schoonover,

2
tak, Charlse, pierwszy sposób działa dobrze, używałem go w wielu miejscach. Jest brudniejszy niż bym chciał, ale jest przydatny. Po prostu użyłem tych liczb całkowitych jako przykładu. Rzeczywiste dane były bardziej zróżnicowane. I if (1 || 2 || 3) {...} else if (4 || 5 || 6) {...} też by działało, ale trudniej je przeczytać.
theo

5
dlaczego uważasz, że ten drugi jest bardziej brudny niż pierwszy. Ten ostatni dodaje jeszcze jedno znaczenie ,i taki, który nie jest współdzielony z żadnym innym językiem w stylu C. Wydałoby mi się to znacznie brudniejsze.
Jon Hanna

1
Być może wybrałeś składnię 2nd z Ruby. Tak to działa w tym języku (chociaż przełącznik zmienia się na wielkość liter, a
wielkość

4
Ważna uwaga . Zakresy są obsługiwane w przypadku przełącznika począwszy C # v7 - Proszę zobaczyć Steve G. za odpowiedź
RBT

Odpowiedzi:


313

Nie ma składni w C ++ ani C # dla drugiej metody, o której wspomniałeś.

W pierwszej metodzie nie ma nic złego. Jeśli jednak masz bardzo duże zakresy, po prostu użyj serii instrukcji if.


5
Jako dodatek chciałem dodać link do specyfikacji języka C # dostępnej w witrynie MSDN pod adresem msdn.microsoft.com/en-us/vcsharp/aa336809.aspx
Richard McGuire,

Użytkownik może użyć niektórych instrukcji if (lub tabeli), aby zredukować dane wejściowe do zestawu wyliczeń i włączyć wyliczanie.
Harvey

5
prawdopodobnie wybrał to z VB.net
George Birbilis

1
VB.net jest lepszy dla różnych instrukcji case ... z wyjątkiem tego, że nie przewijają się jak C #. Daj trochę, daj trochę.
Brain2000

Wierzę, że to już nie jest poprawne. Zobacz stackoverflow.com/questions/20147879/... . Również na to pytanie znajduje się odpowiedź stackoverflow.com/a/44848705/1073157
Dan Rayson

700

Wydaje mi się, że na to już odpowiedziano. Myślę jednak, że nadal możesz mieszać obie opcje w sposób składniowy, lepiej:

switch (value)
{
    case 1: case 2: case 3:          
        // Do Something
        break;
    case 4: case 5: case 6: 
        // Do Something
        break;
    default:
        // Do Something
        break;
}

3
„przełącznik” powinien być małymi literami dla c #?
Austin Harris,

3
Zwinięty kod zostaje przedłużony do pierwszego przykładu w pytaniu. Również może zrobić to tak, jak w pytaniu.
MetalPhoenix

10
Po co się męczyć? Automatyczne wprowadzanie w Visual Studio 2013 i tak przywróci ten format do oryginalnego pytania.
Gustav

14
@T_D otrzymuje wsparcie, ponieważ faktycznie odpowiada na pytanie. OP powiedział: „Czy coś mi umknęło… Carlos odpowiedział tym, czego mu brakowało. Wydaje mi się dość cięty i wysuszony. Nie nienawidzę tego, że ma 422 głosy poparcia.
Mike Devenney

8
@MikeDevenney Następnie zinterpretowałeś pytanie inaczej, o ile widzę poprawną odpowiedź brzmi: „nie, c # nie ma do tego żadnej składni”. Jeśli ktoś zapyta „czy można wlać płyn do szklanki, którą trzymam do góry nogami?” odpowiedź powinna brzmieć „nie”, a nie „możesz wylać płyn, jeśli spojrzysz na niego do góry nogami i użyjesz wyobraźni”, ponieważ w tej odpowiedzi chodzi o użycie wyobraźni. Jeśli używasz zwykłej składni, ale źle ją sformatujesz, wygląda to na inną składnię, z odrobiną wyobraźni. Mam nadzieję, że
rozumiecie

74

Ta składnia pochodzi z Visual Basic Select ... Case Case :

Dim number As Integer = 8
Select Case number
    Case 1 To 5
        Debug.WriteLine("Between 1 and 5, inclusive")
        ' The following is the only Case clause that evaluates to True.
    Case 6, 7, 8
        Debug.WriteLine("Between 6 and 8, inclusive")
    Case Is < 1
        Debug.WriteLine("Equal to 9 or 10")
    Case Else
        Debug.WriteLine("Not between 1 and 10, inclusive")
End Select

Nie można użyć tej składni w języku C #. Zamiast tego musisz użyć składni z pierwszego przykładu.


49
to jedna z niewielu rzeczy, których mi brakuje w * Basic.
nickf

10
Tutaj mamy jeden z niewielu wyświetlaczy, w których Visual Basic nie jest tak brzydki i jest bardziej wszechstronny niż c #. To jest cenny przykład!
bgmCoder

To całkiem przyzwoite. Zastanawiam się, dlaczego nie zostało to dodane do wersji C # 8.0. Byłoby całkiem fajnie.
Sigex,

69

W C # 7 (domyślnie dostępne w Visual Studio 2017 / .NET Framework 4.6.2) przełączanie oparte na zakresie jest teraz możliwe dzięki instrukcji switch i pomogłoby w rozwiązaniu problemu PO.

Przykład:

int i = 5;

switch (i)
{
    case int n when (n >= 7):
        Console.WriteLine($"I am 7 or above: {n}");
        break;

    case int n when (n >= 4 && n <= 6 ):
        Console.WriteLine($"I am between 4 and 6: {n}");
        break;

    case int n when (n <= 3):
        Console.WriteLine($"I am 3 or less: {n}");
        break;
}

// Output: I am between 4 and 6: 5

Uwagi:

  • Nawiasy (i )nie są wymagane w tym whenstanie, ale zostały użyte w tym przykładzie do podkreślenia porównania.
  • varmoże być również używany zamiast int. Na przykład: case var n when n >= 7:.

3
To (dopasowanie wzorca) powinno być ogólnie najlepszą praktyką, gdy można używać C # 7.x lub wyższej, ponieważ jest znacznie bardziej przejrzyste niż inne odpowiedzi.
UndyingJellyfish

Czy można to osiągnąć za pomocą listy Enums? Gdzie Enums mapują na int?
Sigex,

32

Możesz pominąć nowy wiersz, który daje:

case 1: case 2: case 3:
   break;

ale uważam ten zły styl.


20

.NET Framework 3.5 ma zakresy:

Enumerable.Range from MSDN

możesz go używać z „zawiera” i instrukcją IF, ponieważ jak ktoś powiedział, że instrukcja SWITCH używa operatora „==”.

Oto przykład:

int c = 2;
if(Enumerable.Range(0,10).Contains(c))
    DoThing();
else if(Enumerable.Range(11,20).Contains(c))
    DoAnotherThing();

Ale myślę, że możemy się lepiej bawić: ponieważ nie będziesz potrzebować zwracanych wartości, a ta akcja nie przyjmuje parametrów, możesz łatwo używać akcji!

public static void MySwitchWithEnumerable(int switchcase, int startNumber, int endNumber, Action action)
{
    if(Enumerable.Range(startNumber, endNumber).Contains(switchcase))
        action();
}

Stary przykład z tą nową metodą:

MySwitchWithEnumerable(c, 0, 10, DoThing);
MySwitchWithEnumerable(c, 10, 20, DoAnotherThing);

Ponieważ podajesz akcje, a nie wartości, powinieneś pominąć nawias, jest to bardzo ważne. Jeśli potrzebujesz funkcji z argumentami, po prostu zmień typ Actionna Action<ParameterType>. Jeśli potrzebujesz zwracanych wartości, użyj Func<ParameterType, ReturnType>.

W C # 3.0 nie ma łatwej częściowej aplikacji do opisania faktu, że parametr case jest taki sam, ale tworzysz małą metodę pomocniczą (nieco gadatliwą, tho).

public static void MySwitchWithEnumerable(int startNumber, int endNumber, Action action){ 
    MySwitchWithEnumerable(3, startNumber, endNumber, action); 
}

Oto przykład tego, jak nowe funkcjonalne zaimportowane zestawienie są IMHO mocniejsze i bardziej eleganckie niż stare imperatywne.


3
Dobry wybór. Należy jednak zwrócić uwagę - Enumerable.Range ma argumenty int starti int count. Twoje przykłady nie będą działać tak, jak zostały napisane. Piszesz tak, jakby drugi argument był int end. Na przykład - Enumerable.Range(11,20)dałoby 20 liczb zaczynających się od 11, a nie liczb od 11 do 20.
Gabriel McAdams

chociaż, jeśli pracujesz z Enumem, dlaczego nie coś takiego? if (Enumerable.Range (MyEnum.A, MyEnum.M) {DoThing ();} else if (Enumerable.Range (MyEnum.N, MyEnum.Z) {DoAnotherThing ();}
David Hollowell - MSFT

4
Zauważ, że Enumerable.Range(11,20).Contains(c)jest to odpowiednik for(int i = 11; i < 21; ++i){ if (i == c) return true; } return false;Jeśli miałeś duży zasięg, zajęłoby to dużo czasu, a samo użycie >i <byłoby szybkie i stałe.
Jon Hanna

Ulepszenie: MySwitchWithEnumerableZwrot voidjest słabym pomysłem na tę sytuację. POWÓD: Przekształciłeś if-elsew szereg niezależnych instrukcji - które ukrywają zamiar, to znaczy, że wykluczają się one wzajemnie - actionwykonywana jest tylko jedna . Zamiast wrócić boolz ciałem if (..) { action(); return true; } else return false;Witryna nazywając następnie pokazuje intencję: if (MySwitchWithEnumerable(..)) else (MySwitchWithEnumerable(..));. To jest lepsze. Jednak w tym prostym przypadku nie jest to już znacząca poprawa w stosunku do oryginalnej wersji.
ToolmakerSteve

15

Oto kompletne rozwiązanie C # 7 ...

switch (value)
{
   case var s when new[] { 1,2,3 }.Contains(s):
      // Do something
      break;
   case var s when new[] { 4,5,6 }.Contains(s):
      // Do something
      break;
   default:
      // Do the default
      break;
}

Działa również z łańcuchami ...

switch (mystring)
{
   case var s when new[] { "Alpha","Beta","Gamma" }.Contains(s):
      // Do something
      break;
...
}

Oznaczałoby to, że przypisujesz tablice do każdej instrukcji switch, prawda? Czy nie byłoby lepiej, gdybyśmy mieli je jako zmienne stałe?
MaLiN2223,

Elegancko, ale rzeczywiście dobrze byłoby wiedzieć, czy kompilator zoptymalizuje ten scenariusz, aby powtarzane wywołania nie powodowały za każdym razem obciążenia konstrukcji tablicy; zdefiniowanie tablic z wyprzedzeniem jest opcją, ale odbiera wiele elegancji.
mklement0

11

Poniższy kod nie działa:

case 1 | 3 | 5:
// Not working do something

Jedynym sposobem na to jest:

case 1: case 2: case 3:
// Do something
break;

Kod, którego szukasz, działa w języku Visual Basic, w którym możesz łatwo wstawiać zakresy ... w noneopcji switchinstrukcji lubif else bloków wygodnych, sugerowałbym, aby w bardzo ekstremalnym momencie utworzyć .dll z Visual Basic i zaimportować z powrotem do twojego projektu w C #.

Uwaga: odpowiednikiem przełącznika w Visual Basic jest Select Case.


7

Inną opcją byłoby użycie procedury. Jeśli wszystkie przypadki 1-3 wykonują tę samą logikę, zapisz ją w procedurze i wywołaj dla każdej sprawy. Wiem, że tak naprawdę nie pozbywa się instrukcji, ale wdraża dobry styl i ogranicza obsługę do minimum .....

[Edytuj] Dodano alternatywną implementację, aby dopasować oryginalne pytanie ... [/ Edytuj]

switch (x)
{
   case 1:
      DoSomething();
      break;
   case 2:
      DoSomething();
      break;
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

Alt

switch (x)
{
   case 1:
   case 2:
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

5

Jednym z mniej znanych aspektów przełącznika w języku C # jest to, że zależy on od operatora =, a ponieważ można go zastąpić, możesz mieć coś takiego:


string s = foo();

switch (s) {
  case "abc": /*...*/ break;
  case "def": /*...*/ break;
}

4
to może stać się później wielką przeszkodą dla kogoś, kto próbuje odczytać kod
Andrew Harry,

5

gcc implementuje rozszerzenie języka C do obsługi zakresów sekwencyjnych:

switch (value)
{
   case 1...3:
      //Do Something
      break;
   case 4...6:
      //Do Something
      break;
   default:
      //Do the Default
      break;
}

Edycja : Właśnie zauważyłem znacznik C # w pytaniu, więc prawdopodobnie odpowiedź gcc nie pomaga.


4

W C # 7 mamy teraz Dopasowywanie Wzorów, dzięki czemu możesz zrobić coś takiego:

switch (age)
{
  case 50:
    ageBlock = "the big five-oh";
    break;
  case var testAge when (new List<int>()
      { 80, 81, 82, 83, 84, 85, 86, 87, 88, 89 }).Contains(testAge):
    ageBlock = "octogenarian";
    break;
  case var testAge when ((testAge >= 90) & (testAge <= 99)):
    ageBlock = "nonagenarian";
    break;
  case var testAge when (testAge >= 100):
    ageBlock = "centenarian";
    break;
  default:
    ageBlock = "just old";
    break;
}

3

Właściwie nie podoba mi się również polecenie GOTO, ale jest ono w oficjalnych materiałach Microsoft i tutaj są dozwolone wszystkie składnie.

Jeśli osiągnięty zostanie punkt końcowy listy instrukcji sekcji przełączania, wystąpi błąd kompilacji. Jest to znane jako zasada „bez upadku”. Przykład

switch (i) {
case 0:
   CaseZero();
   break;
case 1:
   CaseOne();
   break;
default:
   CaseOthers();
   break;
}

jest ważny, ponieważ żadna sekcja przełącznika nie ma osiągalnego punktu końcowego. W przeciwieństwie do C i C ++, wykonanie sekcji przełącznika nie może „przejść” do następnej sekcji przełącznika, a przykład

switch (i) {
case 0:
   CaseZero();
case 1:
   CaseZeroOrOne();
default:
   CaseAny();
}

powoduje błąd czasu kompilacji. Jeśli po wykonaniu sekcji przełącznika ma nastąpić wykonanie innej sekcji przełącznika, należy użyć jawnego przypadku goto lub instrukcji goto default:

switch (i) {
case 0:
   CaseZero();
   goto case 1;
case 1:
   CaseZeroOrOne();
   goto default;
default:
   CaseAny();
   break;
}

Dozwolone jest stosowanie wielu etykiet w sekcji przełączników. Przykład

switch (i) {
case 0:
   CaseZero();
   break;
case 1:
   CaseOne();
   break;
case 2:
default:
   CaseTwo();
   break;
}

Uważam, że w tym konkretnym przypadku można użyć GOTO i jest to właściwie jedyny sposób na upadek.

Źródło: http://msdn.microsoft.com/en-us/library/aa664749%28v=vs.71%29.aspx


Zauważ, że w praktyce gotoprawie zawsze można tego uniknąć (choć nie uważam tego za „okropny” - pełni określoną, ustrukturyzowaną rolę). W twoim przykładzie, ponieważ umieściłeś ciała w funkcjach (to dobrze), przypadek 0 może się stać CaseZero(); CaseZeroOrOne(); break;. Nie gotowymagane
ToolmakerSteve

Łącze jest w połowie uszkodzone (przekierowuje, „Dokumentacja techniczna wycofana z programu Visual Studio 2003” ).
Peter Mortensen

2

Wygląda na to, że włożono wiele pracy w znalezienie sposobów na uzyskanie jednej z najmniej używanych C # składni, aby wyglądały lepiej lub działały lepiej. Osobiście uważam, że instrukcja switch jest rzadko warta użycia. Zdecydowanie zalecam przeanalizowanie danych, które testujesz, i oczekiwanych wyników końcowych.

Powiedzmy na przykład, że chcesz szybko przetestować wartości w znanym zakresie, aby sprawdzić, czy są to liczby pierwsze. Chcesz uniknąć marnotrawstwa obliczeń i możesz znaleźć listę liczb pierwszych w zakresie, który chcesz online. Możesz użyć ogromnej instrukcji switch, aby porównać każdą wartość ze znanymi liczbami pierwszymi.

Lub możesz po prostu stworzyć mapę tablic liczb pierwszych i uzyskać natychmiastowe wyniki:

    bool[] Primes = new bool[] {
        false, false, true, true, false, true, false,    
        true, false, false, false, true, false, true,
        false,false,false,true,false,true,false};
    private void button1_Click(object sender, EventArgs e) {
        int Value = Convert.ToInt32(textBox1.Text);
        if ((Value >= 0) && (Value < Primes.Length)) {
            bool IsPrime = Primes[Value];
            textBox2.Text = IsPrime.ToString();
        }
    }

Może chcesz sprawdzić, czy znak w ciągu jest szesnastkowy. Możesz użyć nieprzyjemnej i nieco dużej instrukcji switch.

Możesz też użyć wyrażeń regularnych do przetestowania znaku lub użyć funkcji IndexOf do wyszukania znaku w ciągu znanych liter szesnastkowych:

        private void textBox2_TextChanged(object sender, EventArgs e) {
        try {
            textBox1.Text = ("0123456789ABCDEFGabcdefg".IndexOf(textBox2.Text[0]) >= 0).ToString();
        } catch {
        }
    }

Powiedzmy, że chcesz wykonać jedną z 3 różnych akcji w zależności od wartości, która będzie w zakresie od 1 do 24. Sugerowałbym użycie zestawu instrukcji JEŻELI. A jeśli stanie się to zbyt skomplikowane (Lub liczby byłyby większe, takie jak 5 różnych akcji w zależności od wartości z zakresu od 1 do 90), użyj enum, aby zdefiniować akcje i stworzyć mapę tablicową wyliczeń. Wartość zostanie następnie wykorzystana do zindeksowania do mapy tablic i uzyskania wyliczenia żądanej akcji. Następnie użyj małego zestawu instrukcji IF lub bardzo prostej instrukcji switch, aby przetworzyć wynikową wartość wyliczenia.

Zaletą mapy tablic, która konwertuje zakres wartości na działania, jest to, że można go łatwo zmienić za pomocą kodu. Dzięki kodowi przewodowemu nie można łatwo zmienić zachowania w czasie wykonywania, ale z mapą tablic jest to łatwe.


Możesz także zmapować wyrażenie lambda lub delegata
Conrad Frix

Słuszne uwagi. Jeden drobny komentarz: Zazwyczaj łatwiej jest utrzymać listę wartości pasujących do danego przypadku niż mapę tablic. Problem z mapą tablic polega na tym, że łatwo popełnić błąd. Na przykład, zamiast mapy tablic liczb pierwszych true / falses, wystarczy mieć listę liczb pierwszych i załadować je do zestawu HashSet w celu uzyskania wydajności wyszukiwania. Nawet jeśli są więcej niż dwa przypadki, zazwyczaj wszystkie oprócz jednego przypadku jest niewielka lista, więc zbuduj zestaw skrótów (jeśli rzadkie) lub mapę tablicową w kodzie z list innych przypadków.
ToolmakerSteve

1

Aby dodać do rozmowy, korzystając z .NET 4.6.2, mogłem również wykonać następujące czynności. Przetestowałem kod i zadziałał dla mnie.

Możesz także wykonać wiele instrukcji „LUB”, takich jak poniżej:

            switch (value)
            {
                case string a when a.Contains("text1"):
                    // Do Something
                    break;
                case string b when b.Contains("text3") || b.Contains("text4") || b.Contains("text5"):
                    // Do Something else
                    break;
                default:
                    // Or do this by default
                    break;
            }

Możesz także sprawdzić, czy pasuje do wartości w tablicy:

            string[] statuses = { "text3", "text4", "text5"};

            switch (value)
            {
                case string a when a.Contains("text1"):
                    // Do Something
                    break;
                case string b when statuses.Contains(value):                        
                    // Do Something else
                    break;
                default:
                    // Or do this by default
                    break;
            }

Czy to nie zależy od wersji C #, a nie wersji .NET?
Peter Mortensen

1

Jeśli masz bardzo dużą liczbę łańcuchów znaków (lub dowolnego innego typu), które wszystkie robią to samo, polecam użycie listy łańcuchów w połączeniu z właściwością string.Contains.

Więc jeśli masz dużą instrukcję przełączającą taką:

switch (stringValue)
{
    case "cat":
    case "dog":
    case "string3":
    ...
    case "+1000 more string": // Too many string to write a case for all!
        // Do something;
    case "a lonely case"
        // Do something else;
    .
    .
    .
}

Możesz zamienić go na taką ifinstrukcję:

// Define all the similar "case" string in a List
List<string> listString = new List<string>(){ "cat", "dog", "string3", "+1000 more string"};
// Use string.Contains to find what you are looking for
if (listString.Contains(stringValue))
{
    // Do something;
}
else
{
    // Then go back to a switch statement inside the else for the remaining cases if you really need to
}

Skaluje się dobrze dla dowolnej liczby przypadków ciągów.



-5

W tym celu użyłbyś instrukcji goto. Jak na przykład:

    switch(value){
    case 1:
        goto case 3;
    case 2:
        goto case 3;
    case 3:
        DoCase123();
    //This would work too, but I'm not sure if it's slower
    case 4:
        goto case 5;
    case 5:
        goto case 6;
    case 6:
        goto case 7;
    case 7:
        DoCase4567();
    }

7
@scone goto łamie podstawową zasadę programowania proceduralnego (w których c ++ i c # są nadal zakorzenione; nie są to czyste języki OO (dzięki Bogu)). Programowanie proceduralne ma dobrze zdefiniowany przepływ logiki określony przez konstrukcje języka i konwencje wywoływania metod (wzrost i kurczenie się stosu środowiska wykonawczego). Deklaracja goto omija ten przepływ, w zasadzie pozwalając na dowolne przeskakiwanie.
samis,

1
Nie mówię, że to dobry styl, ale nie robi tego, o co prosiło pierwotne pytanie.
scone

2
Nie, nie robi „tego, o co pytało pierwotne pytanie”. Pierwotne pytanie zawierało kod, który działał tak, jak jest . Nie potrzebowali tego naprawić. I nawet jeśli tak, to okropna sugestia. Jest mniej zwięzły i wykorzystuje goto. Co gorsza, jest to całkowicie niepotrzebne użycie goto, ponieważ oryginalna składnia podana przez OP działa. Pytanie brzmiało, czy istnieje bardziej zwięzły sposób na podanie alternatywnych przypadków. Jak ludzie odpowiedzieli wiele lat wcześniej , tak, jest - jeśli chcesz umieścić kilka skrzynek w jednym wierszu case 1: case 2:i jeśli pozwala na to automatyczny styl edytora.
ToolmakerSteve

Jedynym powodem, dla którego goto uznano za złe, jest to, że niektórym trudno jest podążać za logiką. .NET MSIL (złożony kod obiektowy) używa goto wszędzie, ponieważ jest szybki, ale jeśli kod .Net można napisać i zachowywać się tak samo wydajnie bez nich, lepiej go nie używać, aby nie rozpalać ludzi takich jak @ Protekcjonalna odpowiedź ToolmakerSteve.
dynamiclynk

@wchoward - Proszę uważnie przeczytać moją odpowiedź. Moja skarga dotyczy nie tylko użycia goto . Sprzeciwiłem się, ponieważ pytanie pokazało kod, który już działa tak , jak jest , a ta odpowiedź a) bierze ten działający kod i czyni go bardziej szczegółowym i mniej uporządkowanym, bez żadnej korzyści , b) nie odpowiada na pytanie.
ToolmakerSteve
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.