As we learned in the previous post, C# supports a type system. Part of that system is a group of "basic" types. These types, also called primitive types, form the foundation of many C# programs.
The Sample Solution
Number Types
The most basic of the primitive types are the number types. These include integral numeric types (which represent whole numbers, like 1, 67, 1957321, 8, and so on) and floating-point numeric types (which represent non-whole numbers such as 1.2, 6.99, 8234.66, and so on).
Int
Of the integral numeric types, the type int
is the default and most common. int
represents a 32-bit integer, with a positive or negative value.
int five = 5;
int thirteenHundred = 1300;
int negativeForty = -40;
int intMaxValue = int.MaxValue; //(2^31 - 1)
The type int
is used for many kinds of variables, including math, counters, and iterators.
Short, Long, and Byte
short
, long
, and byte
are all integral numeric types, like int
. However, they represent different ranges of values.
A short
represents a 16-bit integer:
short three = 3;
short negativeOneHundred = -100;
short shortMaxValue = short.MaxValue; //(2^15 - 1)
A long
represents a 64-bit integer:
long fifty = 50;
long longMaxValue = long.MaxValue; //(2^63 - 1)
Finally, a byte
is a 8-bit integer that only represents positive values.
byte four = 4;
byte byteMaxValue = byte.MaxValue; //(2^7 - 1)
Signed and Unsigned
Integral types in C# are normally signed, meaning they can represent positive or negative values (the exception to this is byte
, which is unsigned and therefore can only represent positive values).
We can use the types ushort
, uint
, and ulong
to represent unsigned integers, and sbyte
to represent signed bytes.
ushort unsignedShortMax = 65535;
uint unsignedIntMax = 4294967295;
ulong unsignedLongMax = 18446744073709551615;
sbyte signedByteMin = -127;
Floating-Point Numeric Types
In C#, floating-point numeric types represents non-whole or partial numbers. They are used to do more complex math calculations, currency representations, and other places where we need more than simple integers.
Double and Float
A double
is an 8-byte number used when we need quick calculations but don't care about precision (see "A Note About Precision" below).
double fortytwo = 42.0;
double pi = 3.14159;
A float
is used in the same situation as a double
, but it has less range, since it is a 4-byte number. Consequently it can perform calculations within its range even more quickly than double
.
A Note About Precision
When using double
or float
, precision is lost when doing complex calculations. For example, in C# we can do this:
double sum = 0.1 + 0.2;
But we will get a strange result: 0.30000000000000004
When doing arithmetic with double
or float
, precision (which is the accuracy of numbers on the right side of the decimal point) is sacrificed to gain speed. Calculations involving floating-point numbers are computationally expensive, meaning it takes a long time (comparatively) to get a result.
Programming language compilers, including C#'s compiler, take shortcuts when doing these kinds of calculations; these shortcuts dramatically speed up the calculations while not reducing precision too much, except in certain circumstances. Most applications will not care that 0.1 + 0.2 = 0.30000000000000004, because precision is not an absolute requirement for this calculation.
For the vast majority of applications that do not deal with money or currency, we as developers probably don't care about the loss of precision that comes from doing arithmetic using double
or float
; it is most likely small enough to be negligible.
However, there are times when precision cannot be lost, and for those times, we use the decimal
type.
Decimal
A decimal
type is used when we need to keep precision, but don't mind that calculations are more computationally expensive to do. The type decimal
is primarily used for currency or money calculations, since loss of precision would be harmful there.
Mixing Number Types
It is possible to use decimal
, double
, and float
in calculations with the integral types, though there are some rules.
For example, using any integral type and a double
in a calculation results in a value of type double
.
int five = 5;
double fivePointFive = 5.5;
double sum = five + fivePointFive; //Result is type double, value 10.5
This works similarly for float
, though if the resulting value is too large, the type of the result is automatically converted to double
.
As you might have guessed, mixing integral types and decimal
gives a result of type decimal
:
short three = 3;
decimal sixPointSevenTwo = 6.72;
decimal sum = three + sixPointSevenTwo; //Result is type decimal, value 9.72
In general, mixing integral numeric types and floating-point numeric types in math calculations will result in objects which have the floating-point numeric type.
Non-Number Types
Besides the integral numeric types and the floating-point numeric types, there are also several non-number types that C# provides.
Bool
C# includes a type bool
to deal with boolean values (values that must be either true or false).
bool isTrue = true;
bool isFalse = false;
Boolean values are often utilized in boolean logic, which we will demonstrate more of in Part 4 (Operators) of this series.
Char
C# also has a char
type to represent a single text character.
char a = 'a';
char ampersand = '&';
char x = 'x';
char comma = ',';
char semicolon = ';';
String
C# has a string
type that represents a collection of characters.
string sentence = "This is a sentence.";
string otherSentence = "The quick brown fox jumped over the lazy dog.";
Please note that by Microsoft's own definition of a "primitive" type, string
is NOT considered a primitive.
The term "string" comes from the idea of this type being a "string" of characters. In fact, the type string
is implemented as collection of characters, and can be used as though it is an array. We will discuss arrays in Part 12 of this series.
Also, unlike all the other primitive types in this article, string
is a reference type, not a value type, meaning its default value is null
. In a later article in this series, we will see many ways of manipulating strings using a variety of C# operators.
DateTime
The type DateTime
, like type string
, is not considered a "primitive" type but it is so commonly used in C# applications that I felt it was worthy of inclusion in this post.
An instance of DateTime
represents a point in time. Typically, this is expressed as a date and a time.
DateTime date1 = new DateTime();
DateTime date2 = new DateTime(2020, 3, 15); //15 March 2020
DateTime date3 = new DateTime(2020, 3, 15, 10, 30, 00); //15 March 2020, 10:30
There are many ways to create instances of this object; the above is just a few of them. We will see many other ways to manipulate DateTime
objects during a later post.
Literal Values
The C# compiler makes assumptions as to what type a variable has if we do not directly tell it what that type should be. In C#, if we write this code:
var myValue = 7.8;
The type of myValue
will be double
, because double
is the default type for any number with a decimal point.
If we want to create myValue
as type decimal
, we need to declare it with the literal marker M.
var myValue = 7.8M;
There are many types of these literal markers, including:
var myDouble = 5.6D; //double
var myFloat = 2.88F; //float
var myLong = 568373L; //Type long, will be type ulong if the value is too large
var myUnsignedInt = 98765U; //Type uint, will be type ulong if the value is too large
Nullable Types
C# allows for the use of nullable types, where a primitive value type can be either one of its "normal" values or null
. Nullable types are identified with the operator ?
.
Nullable types get the value null
as their default value.
char? a = null;
double? myDouble; //Value will be null
decimal? myMoney = 45.61M;
bool? trueFalseOrNotFound = false;
DateTime? myDate = null;
int? myNumber = null;
float? myFloat = 6.3F;
There are two special constrictions on nullable types:
- We cannot use
var
and make the type nullable AND - Type
string
cannot be nullable, since it is a reference type.
When types are made nullable, the C# compiler gives them two special properties: HasValue
and Value
. HasValue
can be used to check if the variable has a value at all, and Value
gives the non-null value but will throw an exception if the value is indeed null.
int? myValue = 5;
if(myValue.HasValue)
{
Console.WriteLine(myValue.Value); //Output: 5
}
int? myValue2 = null;
if(myValue2.HasValue)
{
Console.WriteLine(myValue.Value); //Line does not execute,
//since myValue2 is null
}
Glossary
- Primitive types - "Basic" C# types, including
int
,char
,bool
,string
and others. - Integral numeric types - C# types which represent whole numbers.
- Floating-point numeric types - C# types which represent partial numbers (e.g. decimals or fractions).
- Signed - A value which can be positive or negative.
- Unsigned - A value which can only be positive.
- Precision - The degree to which values of "numbers after the decimal point" are correct. Types
float
anddouble
sacrifice precision for speed, whereas typedecimal
keeps precision but is more computationally expensive. - Boolean - A value which must be either true or false.
- Literal marker - In C#, a single character which identifies the type of the specified value. For example,
5.67M
identifies this value as typedecimal
. - Nullable types - A value type in C# that allows an object to be null, in addition to its normal value range.
Summary
Basic types in C# include integral numeric types int
, long
, short
, and byte
; floating-point numeric types double
, float
and decimal
; and non-numeric types bool
, char
, and string
, plus others; each has a distinct purpose.
Combining objects of different numeric types in math calculations generally results in an object having the more-general type (e.g. combining an int
and a double
will result in a double
, combining a short
and a decimal
results in a decimal
, etc.)
To specify which type a given value should be, we can use literal markers, such as M
for decimal
or F
for float
. We can also make some objects nullable to allow them to have the value null
in addition to the normal values those types can have.
In the next part of this series, we expand on C#'s type system to show how we can take objects of one type and change them to another type via casting, conversion, parsing, the is
and as
keywords, and more. Check it out!
Happy Coding!