I have question regarding how memory is managed for strong type Generics
List ints1 = new List();
ints1.Add(1); ints1.Add(2); ints1.Add
List<int> ints1 = new List<int>();
I presume as ints1 is initialised with a new keyword (new List()) it becomes a reference type.
Let's clear up on some terminology first:
ints1
is not a type of any sort, but a variable, so it cannot "become a reference type", ever.ints1
, for example, was declared to be of type List<int>
. The static type of a variable never changes.ints1
is List<int>
. The dynamic type of a variable may change whenever a new value or object is assigned to it.In the case of ints1
, both types happen to be equal, but this isn't always so:
ICollection<int> ints3 = new List<int>();
Here, the static type is ICollection<int>
(always), and the dynamic type List<int>
(after the assignment).
Answer to Question 1:
ints1.Add(1); ints1.Add(2); ints1.Add(3);
Where are the values 1,2,3 are stored in memory (are they stored in stack or on heap)?
The official answer is that this should be considered an implementation detail of List<T>
and should not matter to you. All you need to know about is the operations you can perform on a List<T>
(such as Add
, Clear
, etc.) and their characteristics (such as pre-/post-conditions, performance, etc.).
If you still want to know how this type works internally, let's start with this hint:
The
List<T>
class […] implements theIList<T>
generic interface using an array whose size is dynamically increased as required. — MSDN reference page for List<T>, section Remarks
Meaning, you can imagine a List<T>
to be an array of type T[]
that grows its capacity when needed. In the case of ints1
, think of an int[]
. Because int
is a value type, the concrete values in the list (1, 2, 3) will be stored in-place. If it were a reference type, the values would each be stored in a separate location and the array would only contain references.
Note that I haven't mentioned the terms "stack", nor "heap" in the above paragraph. This is because these concepts are another implementation detail, namely one of the .NET platform, that you're not supposed to care too much about. (See Eric Lippert's blog article series, "The stack is an implementation detail", and e.g. my answer to a previous question, "Does new always allocate on the heap in C++ / C# / Java?")
Answer to Question 2:
List can scale its size to any size at run time, which is restricted in int[] at compile time. So, if the values 1,2,3 and stored in stack, if a new item is added to List say 4, it wouldn't be continuous to the first 3 right, so how will ints1 know the memory location of 4?
Again, you're not required to think about that. What matters about List<int>
is whether its particular characteristics and operations that it offers suit your purposes. But I wouldn't really have answered your question if I left it at that, so let's briefly think about this anyway:
When you talk about the "stack", you probably mean an executing thread's call stack. AFAIK, virtual machines and programming languages that make use of such a data structure mostly use it for passing arguments to functions/methods, and perhaps also for storing values that stay local to a function. This is not the same as saying that the call stack also gets used for local variables, because a local variable may contain an object that is returned to the calling function, either via the return value, or via a ref
/out
parameter, etc.
To me, it seems unlikely that a List<int>
object would ever end up on a call stack under normal circumstances (I'm not considering stackalloc or anything else related to unsafe contexts btw.). Remember that List<T>
is a reference type: While it's possible, and perhaps quite likely, that the reference to the actual object ends up on the call stack, most likely the object's data itself won't.
A (perhaps naïve) implementation of IList<T>
that uses fixed-size arrays for item storage, faced with the need to increase its capacity, could dynamically allocate a new, larger array, then copy the contents of the current array over to the new array, then abandon the old array in favor of the new one. That way, all items remain together in one place.