Abhishek anand
Abhishek anand

Reputation: 13

Why list preserves the value of another null list in c#

I was going through differences between Stack and Heap and tried something to understand the reference type and value type variables. What I understood is that a reference type holds the address of value stored in heap and if the value in heap is changed all the reference to that address will point to new value, so I played with below codes but I am wondering why the below code prints a value of 10 when lt1 is set to null:

public void Method4()
{
    List<int> lt1 = new List<int>() { 1, 2, 3, 4 };
    List<int> lt2 = lt1;
    lt1 = null;
    int xx = lt2.Sum();
    Console.WriteLine(xx);
}

But it prints 0 when lt1 is cleared.

public void Method4()
{
    List<int> lt1 = new List<int>() { 1, 2, 3, 4 };
    List<int> lt2 = lt1;
    lt1.Clear();
    int xx = lt2.Sum();
    Console.WriteLine(xx);
}

I think lt2 holds the reference of lt1 and when lt1 is set to null then lt2 should also be null but this is not the case.

What is going on in the background?

Upvotes: 1

Views: 212

Answers (6)

AGB
AGB

Reputation: 2226

You've got a couple of ideas conflated here. For a conceptual overview, I would highly recommend Jon Skeet's Variables, References, and Objects explanation here.

We can also examine each example and comment on what is going on:

Nulling out lt1:

We can break down the following method

public void PrintsTen()
{
    List<int> lt1 = new List<int>() { 1, 2, 3, 4 };
    List<int> lt2 = lt1;
    lt1 = null;
    int xx = lt2.Sum();
    Console.WriteLine(xx);//prints "10"
}

line by line.

First:

List<int> lt1 = new List<int>() { 1, 2, 3, 4 };
  • This is a local declaration statement that declares a variable.
    • Declare a variable lt1 of reference type List<int> inside the PrintsTen scope.
    • Create a new object (the object itself lives on the heap) of type List<int> which contains the values 1, 2, 3, and 4
    • Set the variable lt1 to reference/"point to" the object on the heap we just created with the new portion of the expression

Second:

List<int> lt2 = lt1;
  • This is a local declaration statement that declares a variable.
    • Declare a variable lt2 of reference type List<int> inside the PrintsTen scope.
    • Set the variable lt2 to reference/"point to" the object on the heap that lt1 references/"points to".
      • lt2 references/"points to" the List<int> on the heap.
      • This is the list object we created with the new call in the previous line.

Third:

lt1 = null;
  • This is an simple assignment statement (expression statement which contains a simple assignment expression).
    • Assign the lt1 variable to reference/"point to" null (nothing).
      • This does not change what lt2 points to!

Fourth:

int xx = lt2.Sum();
  • This is a local declaration statement that declares a variable.
    • Declare a variable xx of value type int inside the PrintsTen scope.
    • Set the variable xxequal to the value returned from the invocation of the Sum() method on the object referenced by lt2
      • Because lt2 references/"points to" the list object which contains 1, 2, 3 and 4 the result will be the value 10.

Finally:

Console.WriteLine(xx);
  • This is an invocation statement (expression statement which contains an invocation expression).
  • The WriteLine method prints the value of xx (which is 10) to the console window

Calling Clear() on lt1:

We can follow a similar explanation for the following method

public void PrintsZero()
{
    List<int> lt1 = new List<int>() { 1, 2, 3, 4 };
    List<int> lt2 = lt1;
    lt1.Clear();
    int xx = lt2.Sum();
    Console.WriteLine(xx);//prints "0"
}

line by line as well.

First:

List<int> lt1 = new List<int>() { 1, 2, 3, 4 };
  • This is a local declaration statement that declares a variable.
    • Declare a variable lt1 of reference type List<int> inside the PrintsZero scope.
    • Create a new object (the object itself lives on the heap) of type List<int> which contains the values 1, 2, 3, and 4
    • Set the variable lt1 to reference/"point to" the object on the heap we just created with the new portion of the expression

Second:

List<int> lt2 = lt1;
  • This is a local declaration statement that declares a variable.
    • Declare a variable lt2 of reference type List<int> inside the PrintsZero scope.
    • Set the variable lt2 to reference/"point to" the object on the heap that lt1 references/"points to"
      • lt2 references/"points to" the List<int> on the heap.
      • This is the list object we created with the new call in the previous line.

Third:

lt1.Clear();
  • This is an simple invocation statement (expression statement which contains an invocation expression).
    • Because lt1 references/"points to" the list object which contains 1, 2, 3 and 4, Clear() will empty the list object on the heap to contain no elements.
      • Since this changes the list object itself, and lt2 also points to the that same object, lt2 references/"points to" the empty list.

Fourth:

int xx = lt2.Sum();
  • This is a local declaration statement that declares a variable.
  • Declare a variable xx of value type int inside the PrintsZero scope.
  • Set the variable xxequal to the value returned from the invocation of the Sum() method on the object referenced by lt2
    • Because lt2 references/"points to" the list object which contains no elements the result will be the value 0.

Finally:

Console.WriteLine(xx);
  • This is an invocation statement (expression statement which contains an invocation expression).
  • The WriteLine method prints the value of xx (which is 0) to the console window

Upvotes: 0

Sir Rufo
Sir Rufo

Reputation: 19096

A variable storing a reference of an instance is like a sheet of paper where you write down the adress of a building.

If I make a copy of the sheet of paper then I have 2 sheets (variables) with the same address (reference) but I will not have 2 buildings (instances).

If I paint the building (instance) at the adress (reference) on the copied sheet of paper (variable) in red (setting a property of the instance) also the building (instance) at adress (reference) of the original (variable) will be red, because it was the same building (instance).

Upvotes: 0

Good Night Nerd Pride
Good Night Nerd Pride

Reputation: 8452

When you set lt1 to null you are not changing the value of that variable but only the reference.

With lt1 = null you are telling the compiler that lt1 should no longer point to the List<int> you created in the first line of Method4() but should instead point to nothing (i.e. null).

The difference between lt1 = null and lt1.Clear()is exactly that the first changes the reference lt1 itself whereas the latter changes the value that lt1 is referencing.

Upvotes: 0

Igor
Igor

Reputation: 62213

lt1 = null; // changing the pointer of lt1 does not also change that pointer of lt2

In your 1st example all you are doing is changing the pointer to the List<int> for variable lt1. lt2 still points to that List<int> so it will return 10 when Sum is called. You have not made any actual changes to the underlying object on the heap, only changed the address of lt1 so it no longer points to that object.


lt1.Clear(); // its the same list on the heap but now you are calling a method on that list called Clear which removes the items on that list.

In your 2nd example you have a single List<int> (again) but you call the member on that object to clear its content which will result in an empty list. You have changed the underlying object, not the pointer to that object. This is why the Sum will return 0. An empty list is not the same as a null pointer.

Upvotes: 0

rory.ap
rory.ap

Reputation: 35270

When you do this:

List<int> lt2 = lt1;

You're assigning lt2 to point to the same location in memory that lt1 is currently pointing to. If you then go on to do this:

lt1 = null;

That only means that lt1 no longer points to any location in memory -- more importantly it no longer points to the same location in memory that it previously shared with lt2; however lt2 still points to that location, which is why lt2.Sum() still works.

Clearing the memory reference that lt1 points to does not affect lt2.

Upvotes: 1

Alexander
Alexander

Reputation: 20224

  • lt1 holds the pointer to a list
  • lt2 holds the pointer to a list

If you edit the list items "in lt1", they are also edited "in lt2", because they are edited in the list, not in the pointer.

But you are telling "lt1 should now point to null". This does not mean that lt2 should point to null.

Upvotes: 4

Related Questions