In this lesson we're going to talk about the lifetime of objects - in terms of their references being kept in memory - and how the .NET Framework Runtime manages those memory allocations for you.
To illustrate this point, create a new ASP.NET project called “CS-ASP_038” and create a simple Car class with an object instance of it:
When that last line of code executes creating a new Car() instance, the .NET Framework creates a spot in the computer's memory large enough to hold the new instance of the Car class. The computer's memory has addresses - much like how you have a home address – and these addresses are where the .NET Framework Runtime temporarily stores values, like objects or variables, during the lifetime of the variable or the object. You can summarize this .NET Framework memory allocation process as follows:
Creating a variable/object creates a place in memory large enough for the particular data type (in this case a class instance of Car).
The Framework keeps an address (or “pointer”) of where it put that new instance of car, and then it serves that address back to you - the programmer - so that you can get back to the information in memory whenever you need it (such as when referencing the variable somewhere else in code).
In the illustration below, you can think of the color-coding for each property as the memory address storing that property’s value. You then use that address reference to look up that value whenever you get/set the value for the property in code:
Whenever you see the new keyword, you can take that to mean there is a new instance with its own particular location being created in memory. This might become clearer if you split up the declaration/assignment steps on their own line of code:
Here, the first line of code is creating (1) a reference in memory that is not yet pointing to any particular memory address storing particular values in memory (if given):
It’s not until you use the new keyword that this reference (2) becomes set to a particular memory address. You can now “connect” to the memory reference - not directly the values in memory, but the reference to the values (if present) in memory – wherever you refer to it with the human-readable format provided in code:
One of the key features of C# is something called “Garbage Collection” which cleans up memory registers that no longer have a references pointing to them. This is an automatic process which is very different from earlier languages like C or C++ that required programmers to keep very close tabs on each and every item in memory. References can be dropped whenever an object falls out of scope (such as an object declared local to a method and therefore staying alive only as long as that method is running) or with a specific line of code that “destroys” the object. In this illustration, the .NET Garbage Collector would mark all of these memory registers for deletion – freeing up that space - because the reference no longer exists:
This reference/value relationship has a variety of interesting implications when writing code in C#. One such implication is when you assign one object instance to another, you are actually copying the reference, rather than copying a new set of memory registers:
Now, both myCar and myOtherCar point to the exact same memory registers:
This means that if you change the value for myCar.Make you will also be changing it for myOtherCar.Make, and vice versa. This is very different from how value types work, which copy values to their own unique memory registers:
Also, if you de-reference one of these references (by setting it to null), the other reference still points to the memory registers. And since there is still a reference to those registers, the Garbage Collector keeps the memory intact unless myOtherCar also gets set to null:
It’s important to note that object references are normally removed whenever the .NET Framework gets around to it. In some situations this is an indeterminate point in time. This can sometimes cause problems, especially when the object in memory is holding onto a system resource like a network connection or a file in the file system. Those are scenarios in which you would want to force the garbage collector to immediately do its clean-up process so that you can possibly use those resources for something else. This more deterministic approach to managing memory is somewhat more complicated than just setting all references to null but it’s worth keeping in mind as you move further down the path of your programming career.
Lesson 38 - Understanding Object References and Object Lifetime