Я немного смущен тем фактом, что в C # сборка мусора происходит только с ссылочными типами.
Это не факт. Или, скорее, правда или ложность этого утверждения зависит от того, что вы подразумеваете под «собирать мусор». Сборщик мусора определенно смотрит на типы значений при сборе; эти типы значений могут быть активными и удерживать ссылочный тип:
struct S { public string str; }
...
S s = default(S); // local variable of value type
s.str = M();
когда запускается сборщик мусора, он, безусловно, смотрит на s, потому что он должен определить, что s.str еще жив.
Мое предложение: уточнить точно что вы подразумеваете под глаголом "получает мусор".
GC выбирает только эталонные типы для выделения памяти.
Опять же, это не факт. Предположим, у вас есть экземпляр
class C { int x; }
память для целого числа будет находиться в куче, предназначенной для сбора мусора, и поэтому будет утилизирована сборщиком мусора, когда экземпляр C станет неуткоренным.
Почему вы считаете ложью, что сборщик мусора освобождает только память ссылочных типов? Правильным утверждением является то, что память, которая была выделена сборщиком мусора, освобождалась сборщиком мусора, что, я думаю, имеет смысл. ГК выделил его так, чтобы он отвечал за его очистку.
Так что же происходит с типами значений, поскольку они также занимают память в стеке?
С ними вообще ничего не происходит. С ними ничего не должно случиться. Стек составляет миллион байтов. Размер стека определяется при запуске потока; он начинается с миллиона байтов и остается миллион байтов на протяжении всего выполнения потока. Память в стеке не создается и не уничтожается; изменено только его содержимое.