This isn't properly precise, but it's "good enough" for most day-to-day work:
Value types like structs do not themselves create garbage collection overhead.
If you already have some memory set aside for a struct somewhere, assigning a new value to it doesn't give the compiler or runtime any cause to go reserve memory elsewhere - it already has the space it needs to store the value.
Contrary to a comment above, the
new keyword applied to value types in C# does not mean there's a heap allocation (ie. potential garbage generation) happening. In this context,
new just means "invoke the constructor method to decide how to populate this variable." So these two lines are equivalent:
int myInt = new int();
int myInt = 0;
In both cases, it's the
int myInt part that said "I want somewhere to store an integer," and the two different conclusions just sort out what value to stick in there. (They'd likely both compile to the constant 0 anyway).
More Complicated Answer
This section is based on a pair of in-depth discussions by Eric Lippert.
Despite how often it's claimed "structs are allocated on the stack" (even in Microsoft's own documentation), "stack" is an implementation detail of C# on desktop - there's nothing in the language spec that says every implementation must allocate
structs on the stack. And in fact there are cases where structs get allocated on the heap:
struct is a member of a
class, then it will be stored in the class. This doesn't create additional GC allocations beyond the one for the class instance itself.
If the struct gets "boxed" (eg. if you use Unity's
SendMessage(string methodName, object value) method to send it as payload, that
object value instance is a reference type and gets allocated on the heap). Here it's the boxing that's creating garbage collection entries.
struct is captured in a lambda or anonymous method, or part of an iterator block (you can think of this like the first point - we're creating an instance of an object that can have a lifetime independent of the current scope, so we set aside some memory for it, eg. the iterator object, and the struct it needs gets pulled along for the ride, but isn't itself creating another garbage collection entry beyond the one for the iterator instance)
So what's the real difference with value types like structs? Copy versus reference semantics.
myType foo = newFoo();
foo.baz = 0;
myType bar = foo;
bar.baz = 1;
Did this code create one new
myType instance, so references
bar both point at this instance (ie. now
foo.baz == 1)? It did if
myType is a reference type. That means the
myType instance needs to exist in memory at least as long as the lifetime of every reference to it, which is where garbage collection comes in, to handle these types of "long-lived" storage needs.
Or, did this code create two new
myType instances, and copy the value of
bar (ie. it retains
foo.baz == 0). It did if
myType is a value type. That means that as soon as
bar go out of scope, we can safely let the data they were referencing expire, since there aren't any other references to it. (Even any methods using the data with a
ref modifier would have themselves exited first - the semantics won't let us give the data to something with an independent lifetime like a Coroutine or Thread without copying it to them) The compiler & runtime can reliably treat these values as "short-lived" and avoid creating extra garbage tracking overhead for them.