Primitive types in C# with Examples
Primitive types in programming languages
Primitive types in C#- Some types of data are used so often that many compilers provide a simplified syntax. For example, a whole variable here’s how to create:
System.Int32 a = new System.Int32();
Of course, a similar syntax for announcing and initiating a whole change it seems cumbersome. Fortunately, many compilers (including C#) allow use simpler expressions instead, such as:
int a = 0;
Such code reads much better, and the compiler in both cases generates Writes identical IL code for System.Int32. Data types that are supported are directly compiled by the compiler, called primitive types; they have there are direct analogues in the .NET Framework Class Library (FCL). For example, C# int corresponds to System.Int32, so all of the following code compiles without errors and is converted to the same IL commands:
1 2 3 4 5 6 7 |
int a = 0; // Most convenient syntax System.Int32 a = 0; // Convenient syntax int a = new int(); // Inconvenient syntax System.Int32 a = new System.Int32 (); // Most awkward syntax |
Table1 shows the FCL types and their corresponding C# primitive types. In other languages, types that meet the common language specification (Common Language Specification, CLS), correspond to similar primitive types. However, the language does not have to support types that do not meet the CLS requirements.
Table 1: C # primitive types and corresponding FCL types
Primitive types | FCL-type | CLS compatibility | Description |
sbyte | System.Sbyte | No | 8-bit signed value |
byte | System.Byte | Yes | 8-bit unsigned value |
short | System.Int16 | Yes | Signed 16-bit value |
ushort | System.Uint16 | No | Unsigned 16-bit value |
int | System.Int32 | Yes | Signed 32-bit value |
uint | System.Uint32 | No | 32-bit unsigned value |
long | System.Int64 | Yes | 64-bit signed value |
ulong | System.Uint64 | No | 64-bit unsigned value |
char | System.Char | Yes | 16-bit Unicode character (char never represents an 8-bit value, as in unmanaged C++ code) |
float | System.Single | Yes | IEEE 32-bit floating point value |
double | System.Double | Yes | 64-bit floating point value coy in the IEEE standard |
bool | System.Boolean | Yes | Boolean (true or false) |
decimal | System.Decimal | Yes | 128-bit floating value point of increased accuracy, often used for financial calculations where rounding errors are unacceptable. One digit of a number is a sign the next 96 digits fit
the value itself, the next 8 bits power of 10 divisible by 96-bit number (can be in the range zone from 0 to 28). Remaining digits not used |
string | System.String | Yes | Array of characters |
object | System.Object | Yes | Basic type for all types |
dynamic | System.Object | Yes | For the CLR, type dynamic is identical to type object. However, the C# compiler allows variables of type dynamic participate in dynamic resolution of a type with simplified syntax. |
In other words, we can assume that the C# compiler automatically assumes that all source files have the following using directives:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using sbyte = System.SByte; using byte = System.Byte; using short = System.Int16; using ushort = System.UInt16; using int = System.Int32; using uint = System.UInt32; ... |
I cannot agree with the following statement from the language specification C#: “From a programming style point of view, it is preferable to use keyword, not the full system name of the type “, so I try to use FCL type names and avoid primitive type names. In fact, I wish there were no primitive type names at all, and the developers used only FCL type names. And here are the reasons.
- I came across developers who did not know what keyword was used put them in the code: string or String. In C# this is not important, as the keyword string is converted exactly to FCL type System.String. I also heard what some developers have said about 32-bit operating systems, int was represented as a 32 bit type, and on 64-bit systems, it was 64-bit. bit type. This statement is completely wrong: in C#, the int type is always converts to System.Int32, so it always appears to be 32-bit the type of whatever operating system is running. Using the Int32 keyword in your code will avoid confusion.
- In C# long is System.Int64, but in another language it can be Int16 or Int32. As you know, in C ++ / CLI the long type is treated as Int32. If someone will undertake to read the code written in a new language for themselves, then the appointment code may be misinterpreted by him. Many languages are unfamiliar with the key the word long, and their compilers will not skip the code where it occurs.
- Many FCL types have methods with type names included in their names. For example, the BinaryReader type has methods ReadBoolean, ReadInt32, ReadSingle and so on, while the System.Convert type has the methods ToBoolean, ToInt32, ToSingle, etc. Here is a perfectly acceptable code in which a string containing a float looks like unnatural; I even get the impression that the code is wrong:
BinaryReader br = new BinaryReader (…);
float val = br.ReadSingle (); // The code is correct, but it looks strange
Single val = br.ReadSingle (); // Code is correct and looks ok
- Many programmers writing exclusively in C# often forget that other programming languages can be used in the CLR. For example Wednesday FCL is almost entirely written in C# and the developers from the FCL team introduced methods such as the GetLongLength method of the Array class into the library, rotating an Int64 value that is long in C # but not in other languages programming (e.g. C ++ / CLI). Another example is the LongCount method System.Linq.Enumerable class.
For these reasons, I will only use FCL type names in this article. In many programming languages, the following code will happily compile is lyses and executed:
Int32 i = 5; // 32-bit number
Int64 l = i; // Implicit type conversion to 64-bit value
However, you can decide that it won’t compile. Still, System.Int32 and System.Int64, are not derived from each other. I can reassure you: the code compiles successfully and does everything it should. The point is that the C# compiler is not bad understands primitive types and applies its own rules when compiling the code. In other words, it recognizes the most common programming patterns. and generates such IL commands, thanks to which the source code works as required. First of all, this applies to type casting, literals and operators, examples of which we will look at later.
To begin with, the compiler does an explicit and implicit cast between primitive types, for example:
1 2 3 4 5 6 7 8 9 |
Int32 i = 5; // Implicit casting Int32 to Int32 Int64 l = i; // Implicitly cast Int32 to Int64 Single s = i; // Implicit cast Int32 to Single Byte b = (Byte) i; // Explicit cast Int32 to Byte Int16 v = (Int16) s; // Cast Single to Int16 |
C# allows implicit casting if the conversion is “safe” that is, it is not associated with data loss; an example is a conversion from Int32 to Int64.
However, to convert at risk of data loss, C# requires an explicit cast type. For numeric types, “unsafe” conversion means “associated with loss of precision or magnitude of a number. ” For example converting from Int32 to Byte requires an explicit cast to the type, since for large values Int32 is lost accuracy; requires casting and converting from Single to Int16, since the number Single may be larger than Int16.
To implement casting, different compilers can generate different code. For example, if you cast the number 6,8 of type Single to type Int32, some compilers will generate code that will fit 6 into Int32, while others will round the result is up to 7. By the way, the fractional part is always discarded in C#. Exact the casting rules for primitive types can be found in the specifications.
In addition to casting, the compiler “knows” about another feature of the primitive types: literal notation is applicable to them. Literals on their own are considered instances of the type, so you can call instance methods, for example like this:
Console.WriteLine (123.ToString () + 456.ToString ()); // “123456”
In addition, due to the fact that expressions consisting of literals, are added at the compilation stage, the speed of application execution increases.
1 2 3 4 5 |
Boolean found = false; // In the finished code, found is assigned 0 Int32 x = 100 + 20 + 3; // In the finished code, x is assigned 123 String s = "a" + "bc"; // In the finished code, s is assigned "a bc" |
Finally, the compiler “knows” how and in what order to interpret operators encountered in the code (including +, -, *, /,%, &, ^, |, ==,! =,>, <,> =, <=,<<, >>, ~,!, ++, -, etc.):
1 2 3 4 5 |
Int32 x = 100; // Assignment operator Int32 y = x + 23; // Operators of summation and assignment Boolean lessThanFifty = (y <50); // Less than and assignment operators |
Verifiable and unverifiable operations for primitive types:
Programmers should be well aware that many arithmetic operators walkie-talkies over primitive types can lead to overflow:
Byte b = 100;
b = (Byte) (b + 200); // After that b is 44 (2C hex)
Such an “invisible” overflow is usually not welcome in programming. is, and if it is not detected, the application will behave unpredictably. Occasionally, true (for example, when calculating hash codes or checksums), such overflow is not only acceptable but desirable.
Note:
When doing this CLR arithmetic operation in the first step, all values operands are expanded to 32 bits (or 64 bits if to represent 32-bit operand is not enough). Therefore, b and 200 (for which 32 bits enough) are first converted to 32-bit values, and then summed rush. The resulting 32-bit number (300 decimal, 12C hex), before putting it back into variable b, you need to cast to type Byte. Since in this case C# does not perform implicit type conversion, in the second line introduces the operation of casting to the Byte type.
Each language has its own way of handling overflow. In C and C ++ overflow is not considered an error, and when values are truncated, the application does not tears up its work. But in Visual Basic, overflow is always considered as an error and an exception is thrown when it is detected.
There are IL commands in the CLR to allow the compiler to react differently overflow. For example, adding two numbers is performed by the add command, not overflow response, as well as the add.ovf command, which throws a System.OverflowException when overflowed. Also, the CLR has similar IL commands for subtraction (sub / sub.ovf), multiplication (mul / mul.ovf) and data conversion (conv / conv.ovf).
It is up to the C # programmer to decide how to handle the overflow; overflow checking is disabled by default. This means that the compiler generates IL- for addition, subtraction, multiplication and transformation operations commands without overflow checking. As a result, the code is executed quickly, but the developer must either be sure that there is no overflow, or provide the possibility of its occurrence in your code.
To enable the overflow control mechanism at compile time, add the parameter to the compiler command line / checked +. It tells the compiler that to perform addition, subtraction, multiplications and transformations must be generated IL commands with validation overflow. This code is slower because the CLR takes time to check these operations waiting for overflow. When it occurs, the CLR throws an OverflowException. The application code must provide the correct handling this exception.
However, programmers are unlikely to like the need to enable or disable Enabling overflow checking mode in the entire code. They’d better decide for themselves how react to overflow on a case-by-case basis. And C# offers this mechanism for flexible control of checking in the form of checked and unchecked operators. On-example (assuming the default compiler generates code without checking):
1 2 3 4 5 6 7 |
UInt32 invalid = unchecked ((UInt32) -1); // OK And here's an example using the checked statement: Byte b = 100; // An exception is thrown b = checked ((Byte) (b + 200)); // OverflowException |
Here b and 200 are converted to 32-bit numbers and added; the result that is 300. Then converting 300 to Byte throws an exception OverflowException. If the cast to type Byte is deduced from the checked statement, there will be no exception:
b = (Byte) checked (b + 200); // b contains 44; no OverflowException
Along with the checked and unchecked operators, C# has the same-named statements options to include testable or unverifiable expressions inside block:
1 2 3 4 5 6 7 |
checked {// Beginning of the checked block Byte b = 100; b = (Byte) (b + 200); // This expression is checked for overflow } // End of the checked block |
By the way, inside such a block, you can use the + = operator with Byte, which
simplifies the code a bit:
1 2 3 4 5 6 7 |
checked {// Beginning of the checked block Byte b = 100; b + = 200; // This expression is checked for overflow } |
Note:
Setting the overflow control mode does not affect the operation of the method called inside a checked statement or statement, since the action of the statement (and state mentions) checked applies only to the choice of IL-commands for addition, subtraction, data multiplication and transformation. For example:
checked {
// Suppose SomeMethod is trying to fit 400 into Byte
SomeMethod (400);
// Raise OverflowException in SomeMethod
// depends on the presence of check operators in it
}
I’ve seen quite a few calculations that generate unpredictable results. This usually happens due to incorrect user input or due to the return of unexpected variable values. So, I recommend pro grammarians observe the following rules when using checked statements and unchecked.
Use signed types (Int32 and Int64) instead of unsigned numeric types (UInt32 and UInt64) wherever possible. This will allow the compiler to catch the overflow error. In addition, some library components classes (for example, the Length properties of the Array and String classes) are hardcoded to return signed values and pass those values in the code will require fewer type conversions (and therefore, will simplify the structure of the code and its maintenance). Also, numeric types without sign are incompatible with CLS.
Include in the checked block the part of the code that may overflow due to for incorrect input, for example, when processing requests containing data provided by end user or client machine.
It might also be worth catching the OverflowException so that your application could continue to work correctly after such failures.
Include in the unchecked block those pieces of code that do not overflow creates problems (for example, when calculating a checksum).
In code where there are no checked and unchecked operators and blocks, it is assumed that an exception should occur on overflow. For example, when calculating primes input is known, and overflow is a sign errors.
When debugging your code, set the / checked + compiler option. The application will slow down as the system will monitor the overflow during all code not marked with the checked or unchecked keywords. Finding exception, you can easily detect it and fix the error. In the final when building the application, set the / checked- parameter, which will speed up the execution of placement; no exceptions will be thrown. In order to change checked parameter value in Microsoft Visual Studio, open the properties window of your project, go to the Build tab, click on the Advanced button and install Check for arithmetic overflow / underflow checkbox, as shown in Fig1.
In case performance is not critical for your application, I recommend leaving the / checked option checked even in the final version.
This will protect the application from incorrect data and gaps in the system. security. For example, if calculating the array index uses exception, it is better to get an OverflowException than to call to an invalid array element due to overflow.
Note:
The System.Decimal type stands alone. Unlike many programming languages (including C# and Visual Basic), Decimal is not a primitive type in the CLR. The CLR does not provide IL commands for working with Decimal values. The documentation for. NET Framework is told that the Decimal type has public static member methods Add, Subtract, Multiply, Divide and others, as well as the overloaded operators +, -, *, /, etc.
When compiling code of type Decimal, the compiler generates member calls Decimal that do the real work. Therefore, values of type Decimal processed more slowly than primitive CLR types. In addition, since there are no IL commands to manipulate numbers of type Decimal, neither the operators checked and unchecked, nor the corresponding compiler command line options, and carelessness in operations on the Decimal type can lead to an exception OverflowException.
Similarly, System.Numerics.BigInteger is used in UInt32 arrays to representing a large integer value with no upper or lower bound. Therefore, operations of type BigInteger will never call OverflowException exceptions. However, they can result in an exception being thrown OutOfMemoryException if the variable is too large.