W C# fragmenty programu mogą wykonywać się w dwóch kontekstach:
checked i
unchecked.
Różnica polega na sposobie obsługiwania momentu
przekroczenia zakresu liczbowego liczb całkowitych. Jak wiemy, każdy typ liczbowy ma swoje maksymalne i minimalne wartości. Na przykład:
- dla int to -2 147 483 648 do 2 147 483 647 (int zapiany jest na 32 bitach, jeden bit przeznaczonyjest na znak (+/-), a zatem zakres to -(2^31) do 2^32-1)
- dla byte to 0 do 255 (8 bitów, bez znaku, czyli 2^8-1)
- dla short to -32 768 do 32 767 (16 bitów, jeden bit na znak, czyli zakres od -(2^16) do 2^16 -1)
Jeśli kod wywołuje się w kontekście
checked przekroczenie zakresu danego typu liczbowego rzuci wyjątek
ArtihmeticOverflowException.
Jeśli kod wykonuje się w kontekście
unchecked, przekroczenie zakresu "przekręci" wartość liczby (z int.MaxValue + 1 zrobi się
minus int.MaxValue). Żaden wyjątek nie zostanie rzucony.
Przyjrzyjmy się jak to działa w praktyce. Najpierw świadomie przekroczmy wartość
int:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
using System;
namespace CSharpPractices
{
class Program
{
static void Main(string[] args)
{
int number = int.MaxValue;
number += 1;
Console.WriteLine(number);
Console.ReadKey();
}
}
}
|
Wynikiem działania tego programu będzie:
Teraz umieśćmy kod w kontekście
checked. Okazuje się, że od rzuca wyjątek:
Domyślnie kod wykonuje się w kontekście unchecked. Możemy to zmienić na poziomie ustawień kompilatora. W Visual Studio opcja ta znajduje się w
Projekt -> Properties -> Build -> Advanced -> Check for arithmetic overflow/underflow.
Będąc w kontekście
checked możemy wykonań kod jako
unchecked, umieszczając go w klauzuli unchecked:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
using System;
namespace CSharpPractices
{
class Program
{
static void Main(string[] args)
{
checked
{
int number = int.MaxValue;
unchecked
{
number += 1;
Console.WriteLine(number);
}
}
Console.ReadKey();
}
}
}
|
float
Uwaga: kontekst checked i unchecked dotyczy tylko liczb całkowitych. Liczby zmiennoprzecinskowe zachowują się inaczej.
float nie rzuci nam wyjątku nawet w kontekście checked:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
using System;
namespace CSharpPractices
{
class Program
{
static void Main(string[] args)
{
checked
{
float number = float.MaxValue;
number += 100000000000f;
Console.WriteLine(number);
}
Console.ReadKey();
}
}
}
|
Wynik działania programu to...
...czyli
float.MaxValue
decimal
Podobnie jak miało miejsce z
float, także z
decimal kontekst checked i unchecked nie ma znaczenia. Tyle, że w tym przypadku wyjątek poleci zawsze, niezależnie od tego, że jesteśmy w kontekście unchecked:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
using System;
namespace CSharpPractices
{
class Program
{
static void Main(string[] args)
{
decimal number = decimal.MaxValue;
number += 1m;
Console.WriteLine(number);
Console.ReadKey();
}
}
}
|
Co widać tutaj:
Kiedy używać?
Używając kontekstu checked należy pamiętać, że sprawdzanie wartości liczb nie jest darmowe - jest to
dodatkowa operacja, która pogarsza wydajność naszej aplikacji. Dlatego w większości codziennych zastosowań liczb, gdy nie przewidujemy realnego zagrożenia przekroczenia ich zakresu, lepiej trzymać się kontekstu unchecked.
Oczywiście przekroczenie zakresu może być wynikiem błędu w kodzie. Dlatego niezłym pomysłem może być włączenie kontekstu checked dla aplikacji w buildzie debugowym, a wyłącznie go w buildzie releasowym.
Istnieją dwie podstawowe sytuacje, kiedy przekroczenie zakresu jest spodziewane i jest częścią funkcjonowania programu:
- przekroczenie zakresu ma być z definicji ignorowane przez pewne algorytmy. Są to na przykład algorytmy liczące sumy kontrolne, generatory liczb pseudolosowych czy algorytmy szyfrujące.
- przekroczenie zakresu jest spodziewane, ale ma zostać obsłużone, a nie zignorowane. Przykładem może być prosta aplikacja typu kalkulator, która przekroczywszy zakres liczb ma po prostu poinformować o tym użytkownika, zamiast twierdzić, że 2 147 483 647 + 1 to -2 147 483 648
Pułapka
Używając słów kluczowych checked i unchecked, musimy pamiętać, że sprawdzenie wartości liczb ma miejsce tylko wewnątrz danej klauzuli. Dlatego jeśli w tej klauzuli zawołamy funkcję, jej wnętrze nie będzie już sprawdzane:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
using System;
namespace CSharpPractices
{
class Program
{
static void Main(string[] args)
{
int number = int.MaxValue;
int power;
checked
{
power = Power(number);
}
Console.WriteLine(number);
Console.ReadKey();
}
private static int Power(int number)
{
return number * number;
}
}
}
|
Kod ten działa bez rzucenia wyjątku, ponieważ kontekst checked nie dotyczy wnętrza funkcji Power.
W tym poście wspierałam się:
- https://www.codeproject.com/Articles/7776/Arithmetic-Overflow-Checking-using-checked-uncheck
- https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/checked-and-unchecked
Komentarze
Prześlij komentarz