Давайте сразу начнем с кода. У нас есть вот такой код, давайте разберемся почему он выводит то, что выводит:
class Program { static void Main(string[] args) { int a = 1; ProcessValue(a); Console.WriteLine(a.ToString()); //здесь будет выведено 1 TestClass test = new TestClass(); test.val = 2; ProcessRef(test); Console.WriteLine(test.val.ToString()); //здесь будет выведено 10 TestClass test = new TestClass(); test.val = 3; Process(test); Console.WriteLine(test.val.ToString()); //а здесь будет выведено 3 } static void ProcessValue(int par) { par = 3; } static void ProcessRef(TestClass par) { par.val = 10; } static void Process(TestClass par) { par = new TestClass(); par.val = 100; } } public class TestClass { public int val; }
Вы, наверняка, знаете, что в .Net есть значимые (value) и ссылочные (reference) типы. Первые - размещаются полностью в стеке, а вторые размещаются в управляемой куче, а в стеке размещается только указатель на них.
Второе полезное знание, которое многие, к сожалению не всегда знают или понимают состоит в том, что все параметры по умолчанию передаются в методы по значению, то есть, проще говоря, копируются.
То есть, при передаче в качестве параметра объекта значимого типа, в стеке создается такой же точно объект, и при передаче объекта ссылочного типа происходит то же самое. Но, если в случае значимого типа, объект в стеке и так представляет собой значение, то в объекте ссылочного типа в стеке находится только ссылка на сам объект, соответственно внутри метода все изменения происходят в нем же. Вот иллюстрация процесса:
А почему же тогда в третьем случае результат будет 3, а не 100? Все по тому же, потому что значения передаются КОПИРОВАНИЕМ. То есть, в момент выполнения присваивания внутри функции Process стек у нас будет выглядеть примерно так.
Я немного не корректно нарисовал одинаковые стрелочки, так как при передаче параметра в функцию исходная ссылка копируется, а при создании объекта внутри функции изменяется сама ссылка, но суть происходящего отражена правильно - все действия внутри функции происходят с копией указателя на объект, а не с самим указателем. То есть, test как указывал на объект с val=3, так на него и указывает. Вот еще одна картинка - стек на момент выхода из метода Process()
А как же сделать, чтобы и в третьем случае послы выхода из Process() наш объект содержал новое значение? Сделать это очень просто, достаточно использовать в определении метода и при его вызове ключевое слово ref
class Program { static void Main(string[] args) { TestClass test = new TestClass(); test.val = 3; Process(ref test); Console.WriteLine(test.val.ToString()); //теперь результат - 100 } static void Process(ref TestClass par) { par = new TestClass(); par.val = 100; } }
Теперь параметр в метод Process() будет передаваться по ссылке, то есть par будет представлять собой не копию test (то есть указателя на область памяти, где размещено сам объект), а указатель на этот указатель, таким образов все изменения производимые с параметром внутри метода будут происходить не с копией объекта, а с ним самим.
Комментариев нет:
Отправить комментарий