Full Lectures Set
- C#Lectures - Lecture 1: Primitive Types
- C# Lectures - Lecture 2: Work with text in C#: char, string, StringBuilder, SecureString
- C# Lectures - Lecture 3: Designing Types in C#. Basics You Need to Know About Classes
- C# Lectures - Lecture 4: OOP basics: Abstraction, Encapsulation, Inheritance, Polymorphism by C# example
- C# Lectures - Lecture 5: Events, Delegates, Delegates Chain by C# example
- C# Lectures - Lecture 6: Attributes, Custom attributes in C#
- C# Lectures - Lecture 7: Reflection by C# example
- C# Lectures - Lecture 8: Disaster recovery. Exceptions and error handling by C# example
- C# Lectures - Lecture 9:Lambda expressions
- C# Lectures - Lecture 10: LINQ introduction, LINQ to 0bjects Part 1
- C# Lectures - Lecture 11: LINQ to 0bjects Part 2. Nondeferred Operators
This is the first article from the set of C# lectures that I'm doing in my company for our engineers. It is about C# built-in types.
Primitive Types Basics
Every programming language has its own set of basic, built-in data types using which and object oriented approach we can build more complex data types such as classes and structures. C# has its own set of basic, fundamental types. These types are value types which means that when we pass this type as argument to function, they are copied. In this article, I want to show some important and interesting things about value types.
There are 2 sorts of value types:
- Built in value types
- User defined value types
Basic built-in types in C# are stored under
System namespace, they are called primitive types. Because primitive types are used quite often, C# has a simplified way to create them. Instead of writing code like this:
System.Double doubleDigit = new System.Double();
We can use a simpler way:
double doubleDigit = 0;
Actually, we have several ways to define variables of primitive types in C# and it is up to the developer to decide which one to use:
int i =0;
int j = new int();
System.Int32 k = 0;
System.Int32 j = new System.Int32();
Local variables of primitive types must be initialized before they are used.
In both cases, IL compiler will generate the same IL code when you build your project. All C# primitive types have analogs in .NET Framework Class Library. For example,
int in C# corresponds to
System.Int32 in FCL. The table below shows C# types and their representatives in FCL from MSDN:
|Short Name ||.NET Class ||Type ||Width ||Range (bits) |
|Byte ||Unsigned integer ||8 ||0 to 255 |
|SByte ||Signed integer ||8 ||-128 to 127 |
|Int32 ||Signed integer ||32 ||-2,147,483,648 to 2,147,483,647 |
|UInt32 ||Unsigned integer ||32 ||0 to 4294967295 |
|Int16 ||Signed integer ||16 ||-32,768 to 32,767 |
|UInt16 ||Unsigned integer ||16 ||0 to 65535 |
|Int64 ||Signed integer ||64 ||-9223372036854775808 to 9223372036854775807 |
|UInt64 ||Unsigned integer ||64 ||0 to 18446744073709551615 |
|Single ||Single-precision floating point type ||32 ||-3.402823e38 to 3.402823e38 |
|Double ||Double-precision floating point type ||64 ||-1.79769313486232e308 to 1.79769313486232e308 |
|Char ||A single Unicode character ||16 ||Unicode symbols used in text |
|Boolean ||Logical Boolean type ||8 ||True or false |
|Object ||Base type of all other types || || |
|String ||A sequence of characters || || |
|Decimal ||Precise fractional or integral type that can represent decimal numbers with 29 significant digits ||128 ||±1.0 × 10e−28 to ±7.9 × 10e28 |
Short names for us mean that in any of our code files, we have code like this:
using byte = System.Byte. This is not actually that but you can assume it is. Some experienced developers as Jeffrey Richter recommend to use FCL types always instead of short names. For example, in C#,
System.Int64, but other languages may use
Int32. Also, a lot of methods in FCL types have names that correspond to type names. For example:
All primitive types can be conditionally divided to the following types:
- Integral types:
- Real float-point types:
- Real type with decimal precision:
- Boolean type:
- Character type:
- String type:
- Object type:
We will concentrate our attention on the first five types and not discuss
object in this article.
This is the simplest of the primitive types - it can hold only two values:
false. You can think about
0 if it is more convenient for you. If you convert
int, this is what you will get
true will be
false will be 0.
Bool is the simplest but the most commonly used type in all applications it is used to control the flow of the program and you can see its use in all the conditional operators. Below is the sample code that demonstrates basic operations with
bool bVar = false;
bVar = true;
Console.WriteLine("We are here because our boolean value is true");
bVar = false;
Console.WriteLine("We are here because our boolean value is false");
Char value is 16-bit numerical value. Compiler translates the numeric value to character literal. Unicode characters are used to present most languages of the world plus different specific character sets. Constants of the
char can be written as character literals, hexadecimal escape sequence, Unicode representations or can be casted from input integer.
When I tried to assign to
char value bigger than maximum, I got a compilation error:
But if this will happen in runtime and compiler can't control it, we will have Overflow exception in checked code and reset value otherwise. We will review overflow later in this article.
Following code at sixth cycle will assign value
int i = char.MaxValue - 5;
for (int j = 0; j < 7; j++)
cVar = (char)i;
Variables of integral types contain integers and vary by the size of memory allocated for variable and the signed variable or not. Signed or not defines if variable of integer type can hold negative values. Most commonly used types of integers are
long. Usually, if you need any integer value, you use
int and if size of
int doesn't fit your needs, you define
Int is a 32 bit integer and
long is a 64 bit integer. Any literal within
int interval is considered as
int, integers that are bigger than this are automatically treated as
Longs that are in
int range can be explicitly treated as
longs by using "
L" or "
l" literal at the end:
long lVar = 1L;
In table with types characteristics and below in this article, we get ranges for each primitive type. To decide which one from integral types is needed for you, you may refer to these sections to take a decision.
Real types in C# are the real numbers as they are known in mathematics. They are present by floating-point types of different range and precision. Range and precision is crucial to understand in floating-point types. Range defines the minimum and maximum values that type can hold. Precision defines the number of significant figures the variable holds.
float type also known as single precision real number. If you use characters "
f" or "
F" after digit literal, you explicitly point that the number is of type
float. You should know that by default all real numbers are considered as
Float has precision of seven significant digits. For example, digit
0.123456789 will be rounded to
double type is known as
double precision real number. Suffixes "
d" and "
D" are used after digit literal to point that number is of type
double. The precision of
double is form 15 to 16 significant digits . You should know that
double has special values
decimal type is known as decimal floating-point arithmetic where numbers are presented in decimal system rather than binary one. This type doesn't lose accuracy when storing and processing floating-pointing numbers.
Decimal type has precision from 28 to 29 significant digits . "
m" character at the end of digit literal indicates that the number is of type
All primitive types besides
string are derived from
System.ValueType class which in its turn is derived from
System.Object. It means we can call methods of
System.Object on any primitive type and all primitive types are derived from
System.Object that fits .NET methodology. For example, calling
GetType, you may always get FCL type that corresponds to short name. Also, can
typeof operator be used for getting type of the variable. Children of
ValueType are automatically stored in stack when we declare them. Basing on it, they are very fast and effective.
Following fragment of code shows you some interesting characteristics of few value types in C#:
ushort ushortVar = new ushort();
Console.WriteLine("ushort type" +
" \n\t minimum value: " + ushort.MinValue +
" \n\t maximum value: " + ushort.MaxValue +
" \n\t default value for unassigned variable: " + ushortVar +
" \n\t FCL type: " + ushortVar.GetType() +
" \n\t size in bytes: " + sizeof(ushort));
long longVar = new long();
Console.WriteLine("long type" +
" \n\t minimum value: " + long.MinValue +
" \n\t maximum value: " + long.MaxValue +
" \n\t default value for unassigned variable: " + longVar +
" \n\t FCL type: " + longVar.GetType() +
" \n\t size in bytes: " + sizeof(long));
ulong ulongVar = new ulong();
Console.WriteLine("ulong type" +
" \n\t minimum value: " + ulong.MinValue +
" \n\t maximum value: " + ulong.MaxValue +
" \n\t default value for unassigned variable: " + ulongVar +
" \n\t FCL type: " + ulongVar.GetType() +
" \n\t size in bytes: " + sizeof(ulong));
float floatVar = new float();
Console.WriteLine("float type" +
" \n\t minimum value: " + float.MinValue +
" \n\t maximum value: " + float.MaxValue +
" \n\t default value for unassigned variable: " + floatVar +
" \n\t FCL type: " + floatVar.GetType() +
" \n\t size in bytes: " + sizeof(float));
Here is the result of the program above:
As you can see, we can see using method
System.Object, we can read the FCL type of C#
primitive type. Also using
sizeof operator, we can read the size in bytes that is allocated for
primitive type variable. Also, we get maximum and minimum values for types where it is possible.
(Full version of program, you can see in the attachment.)
There are two kinds of types casting between primitive types:
- Implicit type casting - when compiler automatically casts one type to another. This happens when type casting is "safe" and there is no data loss. For example, casting from
- Explicit type casting - when developer takes a risk and explicitly sets the type to which we convert the value.
short short_value = 10;
int int_value = short_value;
byte byte_value = (byte) short_value;
The rules how C# casts values with "not safe" cases, when the value can be bigger than maximum value of the type to which we cast are described in specification of C# under "Conversions". If you are not sure when doing explicit casting, I recommend you to write short test application with possible input values and try to cast them.
When you work with primitive types, especially types that don't have big range for values such as
byte, you may come to the very sensitive topic called overflow. If for example,
byte variable has value equal to
200 and you add to it
60. What will happen? Maximum value for byte is
255, so we don't have place for
260 here. By default, C# doesn't react with exception to such a fact and simply adds
55 to original value and then when it reaches maximum value, we start from
0. In other words,
200 + 60 = 5. Basing on this fact, you should be very careful when working with such a thing as overflow, sometimes you can use it for your purposes and adopt your application for this, but sometimes this may be very efficiently hidden bug that is hard to catch. To avoid the overflow and get system reaction for it, you may use compiler option
add.ovf which will generate
System.OverflowException when you have it. Also you may use checked instruction for part of code and this code will also generate same exception. As you can see, developer has an option to control the overflow, but you should be familiar with tools that are available for you. See code snippet below that demonstrates the overflow:
byte v = 255;
Console.WriteLine("Insert any positive value:");
int c = Console.Read();
byteVar = (byte)(v + c);
Console.WriteLine("New byte is: " + byteVar);
byteVar = checked((byte)(v + c));
catch (OverflowException e)
Console.WriteLine("Overflow exception catched: " + e.Message);
You can use compiler option /checked+ and then system will control each operation of addition, subtraction, multiplication and type casting for overflow and raise
OverflowException when it happens. If you use this option, the program will work slowly, but I recommend you to use this option at least for testing phase.
P.S. I'm missing
string type here as my next lecture will be about text processing in C# and
string will be described in detail there.
Full listing of small application that shows primitive types example is available as attachment.
- Jeffrey Richter - CLR via C#
- Andrew Troelsen - Pro C# 5.0 and the .NET 4.5 Framework