Welcome to Strings in the.Net Framework.
In this learning activity, you'll learn
how strings are implemented in the.net framework.
When working with strings in C#, it's important to realize what is
happening under the hood, otherwise your application may have some unintended
side effects.
The string data type in C# has some behaviors, which aren't apparent at first.
When writing C# code, you can treat string variables just as you would any
primative variable.
For example, say you start with a string variable and set it
to the value something. Then, you declare another string variable, and set it equal to
the first.
Later, if you decide to change the first variable's value, it won't
have any effect on the second variable's value.
This code behaves as if it were written using a primitive data type, such as an integer.
The integer type, like all C# primitive types, is a value type.
A value type means the value is assigned to the variable itself.
So in the last example, the value of 100 is assigned to the int one variable.
The next line of code
assigns int 1 to a new integer variable called int 2.
Since integers are value types,
this really means the code assigns the current value of int 1 100 to the int 2 variable.
Therefore, the int 2 variable also holds a value of 100.
Then we assign a value of 50 to Int 1.
Assigning a different value to int 1 doesn't affect the
assignment of any other variables. Therefore, the value of int 2 remains 100.
In our code, we have two integer variables declared.
Our code allocates memory in the stack to store these variables.
Since integers are value types,
this means the integer values are stored with the variables in stack memory.
If we go back to the first piece of code, it certainly looks like the same thing
happened with our strings and it effectively is, however, the
technicalities under the hood are a bit different.
The string type may appear to
behave and act like the primitive types but it is, in fact, much different.
First, it's important to realize the string type is a reference type, not a value type.
Reference types are not stored on the stack with the variable they're assigned to.
Instead, reference types are stored in heap memory. What is stored
with the variable, on the stack, is a reference to the actual value.
The first line of
code creates a new string. The string has the value - something.
Since strings are
reference types, it's stored in the heap, not the stack.
Therefore, our code allocates
space somewhere in heap memory and stores something there.
It then assigns a reference to that value, to the string 1 variable.
The second line of code
assigns string 2 to string 1.
This time, the code won't create another string.
Instead, it simply creates another reference to the same string value that the
variable string 1 refers to.
This new reference is then assigned to the string 2 variable.
The next line of code creates a new string, with the value of "something else."
This allocates enough space in the heap to store something
else and assigns a reference to this new string to string 1.
It's important to remember strings are immutable.
Immutable simply means they can't change.
So, you can't change a string once it's defined.
Notice a new string is created to store "something else".
The existing string containing "something" is not modified.
The immutability of strings makes them behave as if they were value types.
This makes writing code using strings much more natural because they behave as expected.
If strings were not immutable, and in the past example the string value
of "something" was changed to "something else," then both string variables, string 1
and string 2, would be equal to something else.
This would be a strange and undesirable side-effect when writing code using string types.
The immutability of strings has some other not-so-nice side effects
in certain situations. One such issue is with the concatination of strings.
Since string values can't change after they're created, concatinating one string to an
existing string always results in the creation of a new string.
A common example is displaying someone's full name when the person's first and last
names are stored in two different variables.
If the first name variable is given the value of "Fred," and the last name variable
is given the value of "Flinstone," when we concatenate the two strings together, we
get a third string containing the value "Fred Flinstone".
This is pretty straightforward.
In this example, we used three variables and three string values
to get the full name.
However, the resulting value squishes the first name
in the last name together.
Usually, we would want to add a space between the first and last name.
Let's change the previous code to do just that
How many strings are used now?
You might guess it's three strings, since there are still only three string variables.
You might also guess 4 strings, because we're using an
additional string to hold the space between the first and last names.
Regardless if you guessed 3 or 4 you are incorrect.
This bit of code actually uses 5 strings to get our value.
Because strings are immutable, so we can't change any of our strings.
We're using the string Fred, Flinstone, a space, and
a string to hold onto the results. So where is the 5th string?
It has to do with string concatenation.
String concatenation is a strictly binary operation.
Binary in this case doesn't mean we have to think about the
ones and zeros that make up pretty much everything in the computer.
A binary operation is a simple operation that works with two inputs resulting in a
single outcome.
For example - addition - 1+1 equals two.
This means in the line of code where we concatenate the strings together,
we actually concatenate two strings together twice.
First, we concatenate first name with the space string literal,
which results in a new string created on the heap.
This new intermediate string is then concatenated with last name, which
results in another new string.
This time, the resulting string is also assigned to
the full name variable.
Notice how during this process, two additional strings are
allocated in heap memory even though they aren't assigned to any variable.
Whenever a string is allocated in the heap, the system uses some memory to
store it.
Technically, a string is a collection of char objects.
Each char object occupies two bytes in memory.
So how many bytes in memory does a string occupy?
A string occupies two times the number of characters, plus some overhead,
about 20 bytes.
The .net framework limits the string size to 2 gigabytes,
which is about one billion characters.
In our previous example, we stored an intermediate string with the value of "Fred."
The string used additional resources from the system.
In this situation, we allocated an additional 30 bytes for this intermediate string.
Do we really need to be concerned about this additional memory?
Unless you're coding in some
specific conditions, such as an embedded system, this amount is negligible.
Also, since the .net framework uses a garbage collector, the additional 30
bytes are freed by the garbage collector sometime after our method returns.
Typically, the additional space needed for intermediate values isn't of much
concern, as the amount of space allocated is negligable.
However, there are several gotcha
situations where string concatenation needs to allocate considerably larger
amounts of memory for intermediate values, that can quickly use all of the
system's memory. Usually these gotcha situations happen when looping over a
collection and building a string from that collection.
Congratulations, you've completed Strings in the .Net Framework.