Primitive Types
Any data types the compiler directly supports are called primitive types. Primitive types map directly to types existing in the Framwork Class Library(FCL).
图1
The primitive type names are short and thus giving some terms of convinience. However, the author doesn’t support the use of it and here are his reasons:
- In common sense of many programmers,
int
represents a 32-bit integer on a 32-bit OS and a 64-bit integer on a 64-bit OS. But this is NOT true for C#. In C#,int
always maps toSystem.Int32
. UsingInt32
will remove potential confusion. - In C#,
long
maps toSystem.Int64
, while in a different language, it maps to anInt16
orInt32
. Someone reading source code in one language could easily misinterpret the code’s intention if he is used to programming in a different language. This is also true for other language, not just C#. - The FCL has many methods that have type names as part of their method names. So you may feel unnatural to use
float val = br.ReadSingle();
although it’s right. (In my view, if you feel OK, then it doesn’t matter a lot)
So, it does have some effect on the developing of coding, we can try to use its real name then.
- In common sense of many programmers,
- 2.
Int32 i = 5;
Int64 j = i; // Implicit cast does happen
Based on the casting rules, this code should NOT be able to compile because nither one of this two type derives from the other. However, it does compile because C# compiler has intimate knowledge of primitive types and applies special rules when compiling the code —— It produces necessary IL to make things happen as expected when it recognize common programming patterns like this.
C# allows implicit casts if the conversion is “safe”, which means no loss of data is possible. If the conversion is potentially unsafe, it requires explicit cast. Keep in mind that different compilers can apply different casting rules so you need to be careful.
Primitive types can be written as literals. A literal is considered to be an instance of the type itself, so it can call the methods of its corresponding type directly.
Checked and unchecked primitive type operations
Byte b = 100; b = (Byte) (b + 200); // Implicit cast in the right parenthesis because no data loss, b now contains 44
The first step requires
b
be expanded to 32-bit value(or 64-bit value if any operand requires more than 32 bits).In most programming scenarios, silent overflow is undesirable. However, in some rare programming scenarios, such as calculating a hash value or a checksum, this overflow is not only acceptable but is also desired.
By default, overflow is NOT checked in C#. But if you use checking version, the IL instructions like
add
will be replaced by their checking versions, likeadd.ovf
. You make the choice.The way to check overflow:
- use the /checked+ compiler switch to turn on/off gloabally
- use code like :
checked(...); unchecked(...); checked{...}
Byte b = 100; b = checked((Byte) (b + 200)); // OverflowException is thrown b = (Byte) checked(b + 200); // No OverflowException checked{ Byte c = 100; c += 200; // Can use += if we use checked block }
Attention: Calling a method within a checked operator or statement has no impact on that method
checked{ SomeMethod(300); // Assume SomeMethod tries to load 300 into a Byte // It will NOT throw an OverflowException if it is not compiled with checked instructions }
Programming Recommendation
- Use signed data types wherever possible. This allows the compiler to detect more overflow/underflow errors. Also, some parts of the class library(such as
Length
properties ofArray
andString
are hard-coded to return signed values. - Explicitly use checked around blocks where an unwanted overflow might occur due to invalid input data. Also,
OverflowException
can be caught as well. - Explicitly use
unchecked
around blocks where overflow is OK.
- Use signed data types wherever possible. This allows the compiler to detect more overflow/underflow errors. Also, some parts of the class library(such as
Reference Types and Value Types
- Value type instances don’t use GC(garbage collector), so their use reduces pressure in the managed heap and reduces the number of collections and application requires over its lifetime.
In .NET Framework SDK ducumentation, any type called a class is a reference type, while each value type is referred to as a structure or an enumeration. All of the structures are immediately derived from
System.ValueType
abstract type, which is itself immediately derived fromSystem.Object
type.struct SomeVal{ public Int32 x;} static void Demo(){ SomeVal v1 = new SomeVal(); // Allocated on the thread's stack!!! new doesn't mean heap as it does in C++ }
In C#, types declared using
struct
are value types, and types declared usingclass
are reference types.SomeVal v1 = new SomeVal(); // line 1 Int32 a = v1.x; SomeVal v2; // line 3 Int32 b = v2.x; // error CS0170: Use of possibly unassigned field 'x'
In fact, both line 1 and 3 produce IL that allocates the instance on the thread’s stack and zeroes the fields. The only difference is that C# “thin