The answer accepted is very good, but a deeper dive in a few points below.
Edited: I removed the recommendation about the string in the struct, it didn’t make sense.
Thus, how to differentiate more correctly value types and reference types?
Value types are considered literals or types that implement deep copy. The number 3 a string "foo" are values, they cannot be changed, there is no need to create a new representation for the value 3, there is only one representation, so when assigned, a copy is made generating another memory space.
The string has a particularity, it is unchanging, people forget that there is only one representation of it in memory, so the assignment of the string to avoid wasting time, copies its memory address and not a new copy of it. Note that "cat" + "dog" implies "catdog" which is a completely different representation (another memory), making concatenating many strings in series an onerous operation.
C# brought the struct from C++, making the struct have the same semantics as value types, that is, it is a copy Shallow that is intended to be a deep copy, the responsibility is for the programmer to ensure that no reference is copied in Shallow way, compiler is responsible for copying the struct during assignment creating a new memory space.
Rule of thumb: Structs are composed only of value types, copying a struct is generating a deep copy of it and its members.
Structs are usually used for purely value types, using references within struct is considered a bad practice, because from struct it is expected that the copy generates new memory addresses and no references to it.
Otherwise use the class to represent your entities.
How CLR actually manages memory allocation for instances and classes and structures separately?
Beware, C# is highly influenced by C++, but without some features that were considered unsafe or impractical.
Notice the use of struct without new, it is a valuetype is in the class signature:
using System;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
namespace Linq
{
struct Teste
{
public int t;
}
class Xpto
{
public int t;
}
class Program
{
static void Main(string[] args)
{
Teste t;
Teste x = new Teste();
Xpto xpto = new Xpto();
// Ok, compilador inicializou por nós.
Console.WriteLine(x.t);
// Exception, valor sendo usado sem ser inicializado.
Console.WriteLine(t.t);
}
}
}
Notice how you can use the struct in the stack without creating a new instance, but in this case the compiler does not initialize the Fields. Most programmers prefer the new, which they keep putting in the stack (in this context). Let’s look at the icing on the cake, the CIL.
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2058
// Code size 27 (0x1b)
.maxstack 1
.entrypoint
.locals init (
[0] valuetype Linq.Teste x
)
IL_0000: ldloca.s x
IL_0002: initobj Linq.Teste
IL_0008: newobj instance void Linq.Xpto::.ctor()
IL_000d: pop
IL_000e: ldloca.s x
IL_0010: ldfld int32 Linq.Teste::t
IL_0015: call void [mscorlib]System.Console::WriteLine(int32)
IL_001a: ret
} // end of method Program::Main
The new did not create a newobj as it does with the classes, this was a decision of C#, if we look calmly at the signature of the structs they do not derive from the Object directly, but from Valuetype. Even if this derives from Object new semantics was inserted to differ from objects, without removing from them the behavior of Object.
.class private sequential ansi sealed beforefieldinit Linq.Teste
extends [mscorlib]System.ValueType
{
// Fields
.field public int32 t
} // end of class Linq.Teste
The problem arises when you bring the maximum to the fore: All types of value are in the Stack. Then I won’t rain on the wet, read the Moderator post, because in C# it is allowed that the compiler decides how to prefer, the language does not use raw pointers and does not matter to the programmer where it was allocated. Interestingly, CIL is a STACK based language, but the C# compiler can box/Unbox value types, and this behavior should be avoided for performance issues. Fields are part of the entity and obviously stick together with it in the heap.
A good compiler when creating a local scope value will always allocate to the stack, unless some major factor exists, but that really doesn’t fit the C#programmer’s concern. The use of Garbage Collector takes away from the programmer’s shoulders to be afraid of losing the value of the stack at the end of the scope, this problem exists in C++. C# is compiled in CIL, in CIL it is transitioning value from one stack to another, and memory is collected in a timely fashion.
Besides, why is there such a distinction? After all, classes and
structures, although they have differences are very similar one with the
another.
Similar yes, equal no! It is very useful to use structs when we think in the direction of functional programming and immutability. When you want a copy of values to be a copy and not just a pointer to the same memory, use struct, but respect the above to keep the expected correct behavior.
If you compare that in C++ they are almost equal, C# gave more function to the struct. The struct in the background is a C inheritance pulled into C++ by compatibility and incorporated into C# to represent an aggregation of values.
Knowing that the important thing is the life time of the object is not always part of the programmer’s science, I believe that. I’ve asked some questions related to this to other programmers and could not answer me =/
– Diego Farias