Unlike value types, a reference type doesn't store its value directly. Instead, it stores the address where the value is being stored. In other words, a reference type contains a pointer to another memory location that holds the data.
A Value Type holds the data within its own memory allocation and a Reference Type contains a pointer to another memory location that holds the real data. Reference Type variables are stored in the heap while Value Type variables are stored in the stack.
Int is certainly not a reference type in C#. It's a numerical struct, which is a value type. When talking about C#, it is incorrect to say int is a reference type.
Array, which itself is derived from System. Object. This means that all arrays are always reference types which are allocated on the managed heap, and your app's variable contains a reference to the array and not the array itself.
Both person
and person2
are references, to the same object. But these are different references. So when you are running
person2 = null;
you are changing only reference person2
, leaving reference person
and the corresponding object unchanged.
I guess the best way to explain this is with a simplified illustration. Here is how the situation looked like before person2 = null
:
And here is the picture after the null assignment:
As you can see, on the second picture person2
references nothing (or null
, strictly speaking, since reference nothing and reference to null
are different conditions, see comment by Rune FS), while person
still references an existing object.
Consider person1
and person2
as pointers to some location in storage. In the first step, only person1
is holding the address of the object from storage and later person2
is holding the address of memory location of object from storage. Later when you assign null
to person2
, person1
stays unaffected. That is why you see the result.
You may read: Value vs Reference Types from Joseph Albahari
With reference types, however, an object is created in memory, and then handled through a separate reference—rather like a pointer.
I will try to depict the same concept using the following diagram.
Created a new object of type person and the person1
reference (pointer) is pointing to the memory location in storage.
Created a new reference(pointer) person2
which is pointing to the same in storage.
Changed the object property Name to new value, through person2
since both references are pointing to the same object,Console.WriteLine(person1.Name);
outputs Shahrooz
.
After assigning null
to person2
reference, it will be pointing to nothing, but person1
is still holding the reference to the object.
(Finally for memory management you should see The Stack Is An Implementation Detail, Part One and The Stack Is An Implementation Detail, Part Two from Eric Lippert)
You have changed person2
to reference null
, but person1
isn't referencing there.
What I mean is that if we look at person2
and person1
before the assignment then both reference the same object. Then you assign person2 = null
, so person 2 is now referencing a different type. It did not delete the object that person2
was referenced to.
I've created this gif to illustrate it:
Because you've set the reference to null
.
When you set a reference to null
, the reference itself is null
.. not the object it references.
Think of them as a variable that holds an offset from 0. person
has the value 120. person2
has the value 120. The data at offset 120 is the Person
object. When you do this:
person2 = null;
..you're effectively saying, person2 = 0;
. However, person
still has the value 120.
Both person
and person2
point to the same object. Therefore when you change the name of either one, both will get changed (since they point to the same structure in memory).
But when you set person2
to null
, you make person2
into a null pointer, so that is does not point to the same object as person
anymore. It wont do anything to the object itself to destroy it, and since person
still points/references the object it wont get killed by garbage collection either.
If you also set person = null
, and you have no other references to the object, it will eventually be removed by the garbage collector.
person1
and person2
point to the same memory address.
When you null person2
, you null the reference, not the memory address, so person1
continues reffering to that memory address, that's the reason. If you change the Classs Person
into a Struct
, the behaviour will change.
I find it most helpful to think of reference types as holding object IDs. If one has a variable of class type Car
, the statement myCar = new Car();
asks the system to create a new car and report its ID (let's say it's object #57); it then puts "object #57" into variable myCar
. If one writes Car2 = myCar;
, that writes "object #57" into variable Car2. If one writes car2.Color = blue;
, that instructs the system to find the car identified by Car2 (e.g. object #57) and paint it blue.
The only operations which are performed directly on object ID's are creation of a new object and getting the ID, getting a "blank" id (i.e. null), copying an object ID to a variable or storage location that can hold it, checking whether two object IDs match (refer to the same object). All other requests ask the system to find the object referred to by an ID and act upon that object (without affecting the variable or other entity that held the ID).
In existing implementations of .NET, object variables are likely to hold pointers to objects stored on a garbage-collected heap, but that's an unhelpful implementation detail because there's a critical difference between an object reference and any other kind of pointer. A pointer is generally assumed to represent the location of something which will stay put long enough to be worked with. Object references don't. A piece of code may load the SI register with a reference to an object located at address 0x12345678, start using it, and then be interrupted while the garbage collector moves the object to address 0x23456789. That would sound like a disaster, but the garbage will examine the metadata associated with the code, observe that the code used SI to hold the address of the object it was using (i.e. 0x12345678), determine that object that was at 0x12345678 had been moved to 0x23456789, and update SI to hold 0x23456789 before it returned. Note that in that scenario, the numerical value stored in SI was changed by the garbage collector, but it referred to the same object before the move and afterward. If before the move it referred to the 23,592nd object created since program startup, it will continue to do so afterward. Interestingly, .NET does not store any unique and immutable identifier for most objects; given two snapshots of a program's memory, it will not always be possible to tell whether any particular object in the first snapshot exists in the second, or if all traces to it have been abandoned and a new object created that happens to look like it in all observable details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With