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
Introduction
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 | Byte | Unsigned integer | 8 | 0 to 255 |
sbyte | SByte | Signed integer | 8 | -128 to 127 |
int | Int32 | Signed integer | 32 | -2,147,483,648 to 2,147,483,647 |
uint | UInt32 | Unsigned integer | 32 | 0 to 4294967295 |
short | Int16 | Signed integer | 16 | -32,768 to 32,767 |
ushort | UInt16 | Unsigned integer | 16 | 0 to 65535 |
long | Int64 | Signed integer | 64 | -9223372036854775808 to 9223372036854775807 |
ulong | UInt64 | Unsigned integer | 64 | 0 to 18446744073709551615 |
float | Single | Single-precision floating point type | 32 | -3.402823e38 to 3.402823e38 |
double | Double | Double-precision floating point type | 64 | -1.79769313486232e308 to 1.79769313486232e308 |
char | Char | A single Unicode character | 16 | Unicode symbols used in text |
bool | Boolean | Logical Boolean type | 8 | True or false |
object | Object | Base type of all other types | | |
string | String | A sequence of characters | | |
decimal | 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#, long
is System.Int64
, but other languages may use Int16
and Int32
. Also, a lot of methods in FCL types have names that correspond to type names. For example: ReadBoolean
, ReadInt32
, etc.
All primitive types can be conditionally divided to the following types:
- Integral types:
sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
- Real float-point types:
float
, double
- Real type with decimal precision:
decimal
- Boolean type:
bool
- Character type:
char
- String type:
string
- Object type:
object
We will concentrate our attention on the first five types and not discuss string
and object
in this article.
Boolean Type
This is the simplest of the primitive types - it can hold only two values: true
or false
. You can think about true
as 1
and false
as 0
if it is more convenient for you. If you convert bool
to int
, this is what you will get true
will be 1
and 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
type:
bool bVar = false;
bVar = true;
if (bVar){
Console.WriteLine("We are here because our boolean value is true");
bVar = false;
}
if (!bVar)
{
Console.WriteLine("We are here because our boolean value is false");
}
Character Type
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 0
to char
variable:
int i = char.MaxValue - 5;
for (int j = 0; j < 7; j++)
{
cVar = (char)i;
i++;
}
Integral Types
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 int
and long
. Usually, if you need any integer value, you use int
and if size of int
doesn't fit your needs, you define long
. 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 long
. Long
s that are in int
range can be explicitly treated as long
s 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
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 double
. Float
has precision of seven significant digits. For example, digit 0.123456789
will be rounded to 0.1234568
.
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 Double.PositiveInfinity
and Double.NegativeInfinity
.
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 decimal
.
Types Characteristics
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 GetType
of 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.)
Types Casting
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
Int32
to Int64
.
- Explicit type casting - when developer takes a risk and explicitly sets the type to which we convert the value.
Samples:
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.
Overflow
When you work with primitive types, especially types that don't have big range for values such as short
or 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);
try
{
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.
Listing
Full listing of small application that shows primitive types example is available as attachment.
Sources
- Jeffrey Richter - CLR via C#
- Andrew Troelsen - Pro C# 5.0 and the .NET 4.5 Framework
- MSDN
- http://www.introprogramming.info/english-intro-csharp-book/read-online/chapter-2-primitive-types-and-variables/
- http://www.dreamincode.net/forums/topic/217947-c%23-primitives-built-in-types