Default literal and non nullable reference types
Hi there š
I create this post to chat about the default literal.
How come that the default literal for every non nullable reference type is null ?
At the root, I see that the line "object obj = default;" makes variable "obj" have "null" in it.
Same for a string or for a custom Type (a class) that I instantiate.
I found this topic : https://stackoverflow.com/questions/63596203/non-nullable-reference-types-default-values-vs-non-nullable-value-types-defaul
And the accepted answer mentions this :
This makes sense from a practical standpoint, as one of the basic usages of defaults is when declaring a new array of values of a given type. Thanks to this definition, the runtime can just zero all the bits in the allocated array - default constructors for value types are always all-zero values in all fields and null is represented as an all-zero reference.
Would it be correct to say that, from a beginner perspective, it would be easier to have the default value of any non nullable reference type set to the default constructor of that type (to avoid null values in code), BUT that it is not like that in C# for practical reasons linked to the performance of C# and its Runtime ?
š
My questions are the following :
1. Is the default literal equal to "null" because the runtime would have to use more memory than it really needs, for every class instantiation, if it was something else ? (I am not sure that I have understood the StackOverflow answer)
2. When do you guys use the "default" literal ? Do you have an example ?Stack Overflow
Non-nullable reference types' default values VS non-nullable value ...
This isn't my first question about nullable reference types as it's been few months I'm experiencing with it. But the more I'm experiencing it, the more I'm confused and the less I see the value ad...
16 Replies
1.
default
is the default value for a given type. For all reference types, that is null
. For value types, it differs. default(int)
is 0
for example
2. Whenever I want something to have the default value, or whenever I'm checking if it has a default value. LINQ method .FirstOrDefault()
for example will return the first element that matches a given predicate, or default
when it doesn't find itIt's not a feature a beginner would care about
setting it to the default constructor makes no sense
default is supposed to be free
constructors can run arbitrary code
use
new()
to call the default constructor
always, not almost always
btw the note about default constructors for structs you have in the question is wrong
default doesn't call constructors
that's kinda the point
it just memsets to 0
structs can have parameterless constructors now, accessible with new()
I am not sure that I understand what it means to be "defined by the standard".
Would it mean that it is C# or the Runtime themselves who define the default value, as in their design itself, instead of for example my custom code who would define the default value ? So that no override is possible, or even not desirable ?
Ok, I thought about default constructors as a way to set the "default value" of an object, like a "starting value" that would be preferable than null. But I think its wrong indeed since the default constructor is designed for that haha
That I understand I think, so for example it memsets the int to only zero's in its 32 bits so its default value is 0, and it memsets the reference type to only zero's in every bit needed for that type so its default value is null, according to C#
Oh, ok, so no instance for this reference, its only zeroes in the memory. So the number of zeroes depends on the memory size needed for that type ?
That's really nice
I mean, I find it interesting, I m not sure I will ever use it like that hehe
Sorry, I may have used the wrong tag "beginner", I just think I m still a beginner that is why. Maybe the question itself is a bit more intermediate.
This question came because of curiosity more than anything else š
And I can already thank you all for the replies, you're awesome
references are the same size
any reference is just an address
structs are stored inline, hence yes
default may amount to more than one thing set to 0
it was in response to "wouldn't it be more beginner friendly for it to call the default constructor" in the question
Oh, ok, my bad š
So doing something like "object obj = default;" uses the variable "obj" to put into it the reference "0" (zero), so... I guess it doesn't allocate anything in the memory ? But the reference is set to "0", so it does point to something inside the memory, or not ? Like, is there a "0" location inside the memory when we use this ?
I m sorry I think my brain is fried haha, maybe my question makes no sense, the more I read it the less I understand.
Maybe I should wait for tomorrow to get back at the question š
the reference (address) being 0 is in fact an invalid address that is commonly used for this purpose
yes, setting any reference doesn't allocate anything
Alright, many thanks ! I understand better now
If I do "object obj = default;" , the variable obj itself has an address if I am correct, otherwise I could not write another code line below this one with something like "obj = 42;" , and the runtime has to store that address somewhere right ?
So does that mean that it kind of allocates a small tiny memory for this ? To store the address of the variable, or I am wrong ?
I m trying to remember what I learnt from the CS50 course but it has been months so its kind of vague (in my MEMORY š)
Don't worry, I love learning new technical terms too haha š
I see, yes, so there is a difference between the reference that the variable "obj" stores and the address of the variable "obj" itself, and the address of the variable is located somewhere on the stack instead of being located on the heap, while the objects are more likely to be located on the heap
Very nice, thank you ! I love the analogy
variables don't store runtime type information, objects on the heap do
there's a difference in how access to them is implemented, but it's the same kind of address in both cases
there's another mechanism here called boxing
it happens when you try to store a value type as a reference type
42 is a value type (stored inline)
object is a reference type
in fact any value type has an associated reference type variant
a regular int would be stored inline
the reference variant of int would be stored on the heap with all runtime info, plus whatever a regular int stores
boxing means instantiating this variant and copying the 42 there, onto the heap, then storing the address of this new object
so even though 42 is of a value type there, that code will allocate, always
Ok š® I heard about boxing, but i thought it was only about casting some type into another type like casting a float as an int, or something like that. Thank you for the clarification šŖ
that's truncation
or flooring
that's probably what you're referring to
I didnt only think about numbers tho, I was referring to all types of casting, even casting a Square to a Rectangle
If we supposed that Square and Rectangle were custom classes that I created
I thought this too was boxing and unboxing like boxing a Square to a Rectangle, and unboxing a Rectangle to a Square
Where class Square : Rectangle { ... } (with inheritance)
Many thanks for the explanations. Really, I appreciate it. I love chatting about this, and it really helps me understand better how things work in C#. ā„ļø
So if I write
int myNumber = 42;
:
- int is a struct type, but ultimately inheriting from the object class at some point since everything is an object in C#.
- the value stored in the variable is an Int32 number, reserved in the stack memory with 32 bits of space.
- the address of the variable "myNumber" is a reference also reserved in the stack memory, with some space.
Now if I write object obj = 42;
:
- object is a reference type.
- the value stored in the variable is an address, pointing to the heap location where the 32 bits are allocated for this number "42".
- the address of the variable "obj" is also reserved in the stack memory, just like the address for the variable "myNumber".
Now if I write int[] a = [ 1, 2, 3, 4, 5 ];
:
- int[] is a reference type, like all Arrays.
- the value stored in the variable is an address, pointing to the heap location where the space is allocated depending on the size of the array, to store the numbers of the array contiguously.
? Question 1 ? ---> Does this mean that the numbers are stored on the heap ?
Now if I write object[] a = [ 1, 2, 3, 4, 5 ];
:
- object[] is a reference type.
- the value stored in the variable is an address, pointing to the heap location where the references to the numbers' boxes are stored.
? Question 2 ? ---> Where are the numbers' boxes stored, in the heap or in the stack ?
? Question 3 ? ---> Is the difference with the previous example (int[] a
) mainly that the numbers are not directly accessed via the array reference from the "a" variable, but that these numbers are indirectly accessed, first via the array reference from "a" and then via the numbers' references stored in the array ?
I hope the "bold" text doesn't scare you, I only tried to structure my questions a little bit š
Don't hesitate to correct me or to nitpick hehe š I like knowing the correct terms and understanding betterint a = 32;
No addresses are ever stored on the stack here. There's a register in the CPU called the stack pointer, all local variable accesses are offsets from it.
the addresses exist, but they aren't stored
you can store them if you make a ref variable
ref int r = ref a;
this would store a reference
Also, structs don't really inherit from object
, their reference variant does. structs themselves don't store type information when stored inlineOk I see, thank you guys for the explanations !!
I m sorry but I didn't understand at all this sentence, I think its too in depth for me, I don't have the fundamentals of computer science š¢ I dont think I understand the word "offsets" here
And here I think I don't understand the words "reference variant" and "stored inline"
Ok my bad, I think I knew this but I forgot it at some point š
thanks !
I did a melting pot with references and value types in my head hehe, but now its way more clear
Thank you again for taking the time to answer me š« it is very interesting
So since the values are stored directly in the variables, if I would write something like
int myNumber = 42;
and then int second = myNumber;
it would mean there are now two different "42" values stored in the stack since it copied the value of myNumber.
And then if I do something like ref int third = ref second;
it would not create a third copy of the "42" value, but it would instead put the reference pointing to "second" inside the "third" variable š
That s curious, I need to try something with the ref keyword, I wonder what is the exact type of "third" and how I can use it
That's crazy, it it still just an int š®it's a number representing a reference typically 64 bit
it's the terms I used when explaining boxing
yes it's one level of abstraction lower
offset means distance from something
relative distance
Basically, every time you call a function, you allocate space (consecutive memory on the stack) for its parameters and locals (also called a stack frame), copy your values there, them bump the stack pointer to contain the address after that new memory. But the order in which you reserve this memory is consistent, so knowing either the start address of those locals or the address after you can reach any one of them. And functions do. They know that the stack pointer is going to have the address right after these locals, so they offset back from that address
like if a function has parameters named a and b
stack pointer before the function call is at, say, 100
when the function is called, the memory at 100 stores a value for
a
and the one at 104 stores a value for b
, and the stack pointer gets bumped past those to 108
but you know they are in this order, a, then b
so you offset from the address in the stack pointer, 108
back
to reach either one
when you want to reference a
, you'd do stack pointer (108) - fixed offset (8)
now that's the idea. I don't remember which way the stack grows thoughOK wow I think I get it
With the stack pointer and the function calls, I think I can now understand better what I saw when I looked at the Intermediate language yesterday with ILSpy, with things like ldstr, stloc and ldloca š®
Thank you !!
you're welcome