C#: unchecked i checked

Dziś następna porcja bardziej egzotycznych słów kluczowych w c#. Słowa unchecked oraz checked służą do kontrolowania czy nie nastąpił overflow podczas operacji arytmetycznych. Wszystkie niepoprawne operacje w klauzuli checked wywołają wyjątek overflow, ponieważ podczas wykonywania obliczeń sprawdzane jest czy wynik wciąż się mieści w zmiennej. Na przykład:

checked
{
    int i = 0;
    while (true)
        i++;
}

Po pewnym czasie, gdy zmienna i przekroczy Interger.Max, zostanie wyrzucony wyjątek.  .NET za każdym razem sprawdza czy zasięg został nieprzekroczony. Powoduje to oczywiście spadek wydajności. Bardziej wydajne rozwiązanie to:

unchecked
{
 int i = 0;
 while (true)
     i++;
}

Pętla nigdy się nie zakończy – wartość zostanie zawinięta po prostu do minimalnej liczby po przekroczeniu Int.Max. Oczywiście unchecked jest dużo szybsze niż checked. Można użyć składni typu unchecked(expression):

int i = 0;
i = unchecked(i++);

unchecked jest szybszy od checked, jednak należy pamiętaj o kilku sprawach:

  1. Domyślnie wszystkie operacje w C# są unchecked (nie trzeba owijać ich w konstrukcję unchecked).
  2. Unchecked używamy gdy w opcjach kompilatora ustawiliśmy checked ale mimo to, dla pewnych operacji chcemy ominąć sprawdzanie.
  3. unchecked\checked możemy zagnieżdżać w sobie. Np. owijamy całą funkcję  w unchecked  a potem pewne kwestie w checked.

Operacje na stałych, spowodują błąd już na etapie kompilacji, np:

int i = int.MaxValue + 1;

Owijając kod w unchecked, pozbywamy się “problemu”:

unchecked
{
    int i = int.MaxValue + 1;
}

Unchecked często używa się do liczenia hashy (GetHashCode)l. Przekroczenie zasięgu w pewnych algorytmach jest dopuszczalne. Jeszcze raz jednak zaznaczam, że dla nowego projektu w VS, unchecked to operacja domyślna i niejawna. Wspomniałem, że użycie unchecked ma sens głównie gdy globalnie np. checked jest ustawiony. Kiedy więc warto ustawić checked globalnie? Moim zdaniem można zastanowić się nad następującym scenariuszem:

  1. checked globalny ustawiony dla debug – ułatwia wyłapywanie błędów.
  2. checked usunięty w release – wydajność.

checked\unchecked działa tylko dla kodu zamieszczonego bezpośrednio w klauzuli. Wszelkie wywołania innych funkcji nie będą sprawdzane np:

checked
{
     Method(); // arytmetyka nie jest w Method sprawdzana
}
private void Method()
{
    int i = 0;
    while (true)
         i++;
}

Ponadto checked\unchecked dotyczy wyłącznie Int\Uint. Zasięg dla decimal jest zawsze sprawdzany (nawet w unchecked – nie ma to znaczenia).  Z kolei operacje na float\double nie są nigdy sprawdzane (ponieważ istnieją tam wartości takie jak Inf, Nan):

// unchecked nie ma znaczenia dla decimal - wyjatek zawsze zwracany
unchecked 
{
 decimal i = decimal.MaxValue;
 while (true)
     i *= 500;
}
// checked nie ma znaczenia dla float - nigdy nie jest sprawdzana arytmetyka
checked
{
 decimal i = decimal.MaxValue;
 while (true)
     i *= 500;
}

Kiedy ma sens wyłapywanie overflow exception? Istnieją algorytmy w których należy ignorować pewną operację gdy nastąpił overflow – musimy mieć więc jakiś sposób detekcji tego. Osobiście jednak jestem przeciwko wyłapywaniu wyjątków jako sposobowi na sterowanie logiki – można to wykonać w inny sposób.

2 thoughts on “C#: unchecked i checked”

Leave a Reply

Your email address will not be published.