String, int, float - все типы значений.Поэтому, когда вы говорите
String s1 = "a";
String s2 = s1;
s1 = "b";
Когда инициализируется s2, значение s1 равно , скопировано в пространство, выделенное для s2.Таким образом, если вы отслеживаете память, вы можете найти шестнадцатеричное представление «a» в двух различных местах в памяти (местоположение, выделенное для s1, и память, выделенное для s2).Но если вы попытаетесь сделать то же самое со ссылочным типом, таким как List, то, что произойдет, когда вы скажете что-то вроде этого, будет передача по ссылке:
List<String> ls1 = new List<String>();
ls1.Add("a");
List<String> ls2 = ls1;
ls1.Add("b");
, если объект, соответствующий списку, выделен накучаАдрес, где найти этот объект, находится в пространстве, выделенном для локальных переменных s1 и s2, вместо фактического значения, находящегося в этом пространстве.Причина в том, что объект (List) может быть большим и потенциально долгое время жизни программы, и для такого объекта было бы дорого выделять память вне стека.Я рекомендую вам прочитать эту ветку вопросов , чтобы понять, как типы значений и ссылочные типы действительно интерпретируются CLR