понедельник, 15 декабря 2014 г.

О передаче параметров в C#

Давайте сразу начнем с кода. У нас есть вот такой код, давайте разберемся почему он выводит то, что выводит:

 
    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()

Пи выходе из Precess() все что было создано внутри метода выходит из области видимости и уничтожается а остается то, что было до входа в него. То есть, в нашем случае, объект с val=3.

А как же сделать, чтобы и в третьем случае послы выхода из 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 (то есть указателя на область памяти, где размещено сам объект), а указатель на этот указатель, таким образов все изменения производимые с параметром внутри метода будут происходить не с копией объекта, а с ним самим.

Комментариев нет:

Отправить комментарий