Lesson 4 - More on the type system: Data types in C# .NET
In the previous exercise, Solved tasks for C# .NET lessons 1-3, we've practiced our knowledge from previous lessons.
Lesson highlights
Are you looking for a quick reference on data types in C# .NET instead of a thorough-full lesson? Here it is:
Creating variables of basic whole-number data types:
{CSHARP_CONSOLE}
byte a = 15; // can store numbers from 0 to 255
short b = 10000; // -32 768 to 32 767
int c = 500000; // -2 147 483 648 to 2 147 483 647
long d = 10000000000; // -9 223 372 036 854 775 808 to 9 223 372 036 854 775 807
Console.WriteLine(a + b); // We can use basic arithmetics
Console.ReadKey();
{/CSHARP_CONSOLE}
Creating decimal variables:
{CSHARP_CONSOLE}
float f = 3.141592f; // single precision
double d = 3.14159265358979; // double precision
Console.WriteLine(f);
Console.WriteLine(d);
Console.ReadKey();
{/CSHARP_CONSOLE}
Declaring other built-in data types:
{CSHARP_CONSOLE}
string s = "This text can be as long as we want";
char a = 'A'; // One character
decimal d = 12.44m;
bool loveCsharp = true; // bools are true or false
Console.WriteLine(s);
Console.WriteLine(a);
Console.WriteLine(d);
Console.WriteLine(loveCsharp);
Console.ReadKey();
{/CSHARP_CONSOLE}
Calling String
methods
(StartsWith/EndsWith/Contains/ToUpper/ToLower/Replace):
{CSHARP_CONSOLE}
string s = "Think twice, code once ";
Console.WriteLine(s.StartsWith("Think"));
Console.WriteLine(s.EndsWith("once"));
Console.WriteLine(s.Trim().EndsWith("once"));
Console.WriteLine(s.Contains("TWICE"));
Console.WriteLine(s.ToUpper());
Console.WriteLine(s.ToLower());
Console.WriteLine(s.Replace("Think", "Learn"));
Console.WriteLine("My favorite quote is: {0} with {1} characters", s, s.Length);
Console.ReadKey();
{/CSHARP_CONSOLE}
Would you like to learn more? A complete lesson on this topic follows.
In the previous lesson, Solved tasks for C# .NET lessons 1-3, we learned basic data types of C# .NET
(int
, double
and string
). In today's
tutorial, we're going to look at them in more detail and explain how to use them
correctly. Today is going to be more theoretical, and the next lesson will be
very practical. At the end, we'll make a few simple examples.
C# recognizes two kinds of datatypes, value and reference.
Value datatypes
We can easily imagine a variable of the value data type. It can be, for example, a number or a character. The value is stored directly in memory and can be accessed directly from the program. Note how I've used the word "directly" so many times. In the lessons throughout this course, we'll mainly be working with these variables.
Whole-number data types
Let's look at the table of all of the built-in whole-number data types in
.NET, notice the type int
, which we already now.
Data type | Range | Size | .NET type |
---|---|---|---|
sbyte | -128 to 127 | 8 bits | System.SByte |
byte | 0 to 255 | 8 bits | System.Byte |
short | -32 768 to 32 767 | 16 bits | System.Int16 |
ushort | 0 to 65 535 | 16 bits | System.UInt16 |
int | -2 147 483 648 to 2 147 483 647 | 32 bits | System.Int32 |
uint | 0 to 4 294 967 295 | 32 bits | System.UInt32 |
long | -9 223 372 036 854 775 808 to 9 223 372 036 854 775 807 | 64 bits | System.Int64 |
ulong | 0 to 18 446 744 073 709 551 615 | 64 bits | System.UInt64 |
Note: You don't have to remember all those insane numbers from the table. Visual Studio IntelliSense tool almost always helps you. When you hover the cursor over the data type and wait, it shows you a tooltip over it:
You could also read about it in the documentation that Visual Studio provides. The documentation can be opened when you write a particular data type, select its text and press F1.
By now, you might be thinking - why do we have so many data types for storing
numbers? The answer is simple, it depends on their size. If the number is large,
it consumes more memory. For user's age, we should select byte
since nobody can live more than 255 years. Imagine a database with millions of
users of some informational system. If we choose int
instead of
byte, it'll occupy 4 times more space. Conversely, if we have a function that
calculates a factorial, the range of int will not be enough for us and we'll
have to use long
.
Notice that some types are prefixed with u
. They are almost the
same as their "twins" without u
, but they don't allow storing
negative values. Therefore, we're able to store a number twice as large in the
positive part. The ones that can't store negative values are called unsigned and
the ones that can are called signed variables (signed in the
sense that it "stores" the positive or negative sign).
The .NET type is the name of the corresponding structure in the .NET libraries. We use "aliases", to simplify our work. In other words, this:
int a = 10;
is internally treated as this:
System.Int32 a = 10;
Of course, we'll use these aliases, that's what they're there for!
We don't have to think hard about choosing data types, we'll use
int
almost every time. You should think about it only in case the
variables are in some array or collection in general, and there are a lot of
them. In that case, it's worth it to consider memory requirements. The tables I
gave here are mainly for the sake of completeness. The already-mentioned
implicit conversion also works between the types, so we can assign some
int
to a long
variable directly, without having to
convert it.
Decimal numbers
For decimal numbers, the choice is simpler, we can only choose between two
data types. They differ in the range of values, and also in precision, i.e. in
the number of decimal places. The datatype double
is twice as
precise as float
, which you probably deduced from its name.
Data type | Range | Precision | .NET type |
---|---|---|---|
float | +-1.5 * 10−45 to +-3.4 * 1038 | 7 numbers | System.Single |
double | +-5.0 * 10−324 to +-1.7 * 10308 | 15-16 number | System.Double |
Beware, due to the fact that decimal numbers are stored in your computer in a binary system, there is some precision loss. Although the deviation is almost negligible, if you're programming, e.g. a financial system, don't use these data types for storing money since it could lead to slight deviations.
When we want to assign a value to a float
variable in source
code, we have to use the F
suffix. With double
, we may
use the D
suffix, but it can be omitted since double is the default
decimal type:
float f = 3.14F; double d = 2.72;
As the decimal separator in source code, we use dots, regardless of our OS regional settings.
Other built-in data types
Let's look at the other data types that .NET offers:
Data type | Range | Size/Precision | .NET type |
---|---|---|---|
char | U+0000 to U+ffff | 16 bits | System.Char |
decimal | +-1.0 * 10−28 to +-7.9 * 1028 | 28-29 numbers | System.Decimal |
bool | true or false | 8 bits | System.Boolean |
Char
Char represents one character, unlike string
, which represents
the entire string of char
s. We declare characters with apostrophes
in C#:
char c = 'A';
A single, non-array, char
actually belongs in the list of
whole-number variables. It contains a numeric character code, but it seemed more
logical for me to introduce it here. A char
can be returned e.g. by
the Console.ReadKey()
method.
Decimal
The decimal
data type solves the problem of storing decimal
numbers in binary form, the number is stored internally similarly as text. It is
used for storing monetary values. For all other mathematical operations with
decimal numbers, we use float
or double
. To write
decimal
values we use the m
suffix:
decimal m = 3.14159265358979323846m;
Bool
Variables of the bool
type can contain only two values:
true
or false
. We'll use them when we get to
conditions. In a variable of the bool
type, we can store either
true
/false
or a logical expression. Let's try a simple
example:
{CSHARP_CONSOLE}
bool b = false;
bool expression = (15 > 5);
Console.WriteLine(b);
Console.WriteLine(expression);
Console.ReadKey();
{/CSHARP_CONSOLE}
The program output:
Console application
False
True
We write expressions in parentheses. Notice that the expression applies, is
equal to true
since 15
really is more than
5
. Going from expressions to conditions isn't a far stretch, but
we'll go into them in the next lesson.
Reference data types
We'll get to reference data types, but not until we get to object-oriented
programming, where we'll also explain basic differences. For now, we'll work
with simple types, with differences that we won't be able to see. Let's just say
that reference types are more complex than value types. One such type is already
known to us, it's a string
. You might think how come that
string
doesn't have a length limit. It is due to differences in
work with reference data types in memory.
String
provides a wide range of truly useful methods. We'll show
some of them and try them out:
String
StartsWith()
,
EndsWith()
and Contains()
We can ask if a string starts with, ends with or contains a substring. A
substring is a part of a string. All of these methods will take a substring as a
parameter and return bool
(true
/false
).
We can't react to the output yet; however, let's write the return values
nonetheless:
{CSHARP_CONSOLE}
string s = "Rhinopotamus";
Console.WriteLine(s.StartsWith("rhin"));
Console.WriteLine(s.EndsWith("tamus"));
Console.WriteLine(s.Contains("pot"));
Console.WriteLine(s.Contains("lol"));
Console.ReadKey();
{/CSHARP_CONSOLE}
The program output:
Console application
False
True
True
False
We can see that everything works as expected. The first phrase failed, as expected, because the string actually starts with a capital letter.
ToUpper()
and
ToLower()
Distinguishing between capital and lowercase letters is not always what we
want. We'll often need to ask about the presence of a substring in a
case-insensitive way. The situation can be solved using the
ToUpper()
and ToLower()
methods which return the
string in uppercase, resp. lowercase. Let's make a more realistic example than
Rhinopotamus. The variable will contain a line from some configuration file,
which was written by the user. Since we can't rely on the user's input we'll try
to eliminate possible errors, here by ignoring letter cases.
{CSHARP_CONSOLE}
string config = "Fullscreen shaDows autosave";
config = config.ToLower();
Console.WriteLine("Will the game run in fullscreen?");
Console.WriteLine(config.Contains("fullscreen"));
Console.WriteLine("Will shadows be turned on?");
Console.WriteLine(config.Contains("shadows"));
Console.WriteLine("Will sound be turned off?");
Console.WriteLine(config.Contains("nosound"));
Console.WriteLine("Would the player like to use autosave?");
Console.WriteLine(config.Contains("autosave"));
Console.ReadKey();
{/CSHARP_CONSOLE}
The program output:
Console application
Will the game run in fullscreen?
True
Will shadows be turned on?
True
Will sound be turned off?
False
Would the player like to use autosave?
True
We can see that we're able to detect the presence of particular words in a string. First, we convert the entire string to lowercase or uppercase, and then check the presence of the word in lowercase or uppercase, respectively. By the way, simple processing of configuration script may actually look like this.
Trim()
,
TrimStart()
and StrimEnd()
Another issue that may arise with user inputs is accented characters, but fortunately, C# works in UTF-8. Which means that diacritics can hardly corrupt our code. Always consider that most the people around the world don't speak English as their primary language, so your application should support their regional characters if you want them to use it...which you should. Another pitfall can be whitespace characters, which are not visible for users, but can cause program errors. Generally, it's a good idea to trim any input from the user, we can trim either all whitespace characters around the entire string or only leading/trailing ones. C#, in parsing functions, automatically trims a specified string before it starts parsing it. Try to enter some spaces before the number and after the number in the following application:
{CSHARP_CONSOLE}
Console.WriteLine("Enter a number:");
string s = Console.ReadLine();
Console.WriteLine("Here's what you originally wrote: " + s);
Console.WriteLine("Your text after the trim function: " + s.Trim());
int a = int.Parse(s);
Console.WriteLine("I converted the text you entered to a number. Here it is: " + a);
Console.ReadKey();
{/CSHARP_CONSOLE}
Replace()
Probably the most important method on string
is a replacement of
its parts with another text. We enter two substrings as parameters, the first
one is the one want to be replaced and the second one will replace it. The
method returns a new string
in which the replacing occurred. When
the method doesn't find the substring, it returns the original string. Let's
try:
{CSHARP_CONSOLE}
string s = "Java is the best!";
s = s.Replace("Java", "C#");
Console.WriteLine(s);
Console.ReadKey();
{/CSHARP_CONSOLE}
We'll get:
Console application
C# is the best!
Format()
Format()
is a very useful method that allows us to insert
placeholders into a string. The placeholders are represented by numbers in curly
brackets, the first number is 0
. The first parameter is the string
containing those placeholders. Next method parameters will be the variables
which are going to be placed in the text instead of these placeholders. Notice
that the method is not called on the specific variable but right on a
string
type (to be completely accurate it's not called on the
instance, see other courses).
{CSHARP_CONSOLE}
int a = 10;
int b = 20;
int c = a + b;
string s = string.Format("When we add up {0} and {1}, we get {2}", a, b, c);
Console.WriteLine(s);
Console.ReadKey();
{/CSHARP_CONSOLE}
The program output:
Console application
When we add up 10 and 20, we get 30
The console can receive a text in this format by itself, so we can write:
{CSHARP_CONSOLE}
int a = 10;
int b = 20;
int c = a + b;
Console.WriteLine("When we add up {0} and {1}, we get {2}", a, b, c);
Console.ReadKey();
{/CSHARP_CONSOLE}
This is a very useful and clear way to build a string, It's definitely worth
it to use this method instead of conventional concatenation, using the
+
operator, if we don't need high performance.
PadLeft()
and
PadRight()
Now, let's mention methods which do the exact opposite, i.e. add whitespaces
into text What is it good for?
Imagine that we have 100 variables and we want to arrange them into a table. We
modify the text using the PadRight()
method with a column width
parameter, e.g. 20 characters. If the text had e.g. only 12 characters, 8 spaces
would be inserted before it to make it 20 characters long. The
PadLeft()
method would add 8 spaces after the text. Since we
haven't gone over what is needed to make such a table, we'll just keep these
methods in mind. We'll use them later.
The Length
property
Lastly, but most importantly we have the length
property, notice it's not a method. It returns an integer that
represents the number of characters in the string. We don't write parenthesis
after the properties since they have no parameters.
{CSHARP_CONSOLE}
Console.WriteLine("Type in your name:");
string name = Console.ReadLine();
Console.WriteLine("Your name is {0} characters long.", name.Length);
Console.ReadKey();
{/CSHARP_CONSOLE}
There's still a lot to go over and lots of other data types that we haven't covered. Regardless, there is a time for everything. In the next lesson, Solved tasks for C# .NET lesson 4, we'll introduce conditions and then loops, then we'll have enough knowledge to create interesting programs
In the following exercise, Solved tasks for C# .NET lesson 4, we're gonna practice our knowledge from previous lessons.