I have question regarding how memory is managed for strong type Generics
List<int> ints1 = new List<int>();
ints1.Add(1); ints1.Add(2); ints1.Add(3);
int[] ints2 = new int[10]();
ints2.Add(6); ints2.Add(7); ints2.Add(8);
Question 1: I presume as ints1 is initialised with a new keyword (new List<int>()
) it becomes a reference type. Where are the values 1,2,3 are stored in memory (are they stored in stack or on heap)?
Question 2: I know of a thing between List<int>
and int[]
, List<int>
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<int>
say 4, it wouldn't be continuous to the first 3 right, so how will ints1 know the memory location of 4?
Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.
Generics shift the burden of type safety from you to the compiler. There is no need to write code to test for the correct data type because it is enforced at compile time. The need for type casting and the possibility of run-time errors are reduced. Better performance.
With using generics, your code will become reusable, type safe (and strongly-typed) and will have a better performance at run-time since, when used properly, there will be zero cost for type casting or boxing/unboxing which in result will not introduce type conversion errors at runtime.
In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs.
I presume as ints1 is initialised with a new keyword
new List<int>()
it becomes a reference type
This presumption is incorrect. You can use the "new" keyword on a value type too!
int x = new int();
Using "new" does not make anything a reference type. You can use "new" with reference types or value types. What "new" indicates is that storage is going to be allocated and a constructor is going to be called.
In the case of using "new" on a value type, the allocated storage is temporary storage. A reference to that temporary storage is passed to the constructor, and then the now-initialized result is copied to its final destination, if there is one. ("new" is usually used with an assignment but it need not be.)
In the case of a reference type, storage is allocated twice: long-term storage is allocated for the instance and short-term storage is allocated for the reference to long-term storage of the instance. The reference is passed to the constructor, which initializes the long-term storage. The reference is then copied from short-term storage to its final destination, if there is one.
What makes List<int>
a reference type is that List<T>
is declared as a class.
Where are the values 1,2,3 are stored in memory (are they stored in stack or on heap)?
We've worked hard to make a memory manager that lets you not care where things are stored. Values are stored in either a short-term memory pool (implemented as the stack or registers) or a long-term memory pool (implemented as a garbage-collected heap). Storage is allocated depending on the known lifetime of the value. If the value is known to be short-lived then its storage is allocated on the short-term pool. If the value is not known to be short-lived then it must be allocated on the long-term pool.
The 1, 2, 3 owned by the list could live forever; we do not know whether that list is going to outlive the current activation frame or not. Therefore the memory to store the 1, 2, 3 is allocated on the long-term pool.
Do not believe the lie that "value types are always allocated on the stack". Obviously that cannot be true because then a class or array containing a number could not survive the current stack frame! Value types are allocated on the pool that makes sense for their known lifetime.
List<int>
can scale its size to any size at runtime unlikeint[]
Correct. It is educational to see how List<T>
does that. It simply allocates an array of T larger than it needs. If it discovers that it guessed too small, it allocates a new, larger array and copies the old array contents to the new one. A List<T>
is just a convenient wrapper around a bunch of array copies!
if the values 1,2,3 were stored in stack, and a new item 4 is added to the list, then it wouldn't be continuous to the first three.
Correct. That's one reason why the storage for values 1, 2, 3 are not allocated on the stack. The storage is actually an array allocated on the heap.
so how will the list know the memory location of item 4?
The list allocates an array that is too big. When you add a new item, it sticks it into unused space in the too-big array. When the array runs out of room, it allocates a new array.
The "new" syntax is used to initialize both value-types and reference-types. The new list is created on the heap; the values are loaded on the stack (i.e. before they are added to the list), but once added, they are on the heap, in the int[]
that underpins the list. Arrays are always on the heap.
The fact that they are copied to the array also answers part 2 I believe. The array is over-sized, and reallocated only when full.
Note; List<int>
doesn't "become" a reference-type; it is always a reference-type.
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