Содержание
Переменная — область памяти, в которой храниться изменяемое значение. Понятие переменных включает в себя локальные переменные, параметры, свойства классов, и элементы массива.
Стек (Stack) и хип (Heap)
Переменные и константы хранятся в стеке или хипе. Стек (стопка) — блок памяти для хранения локальных переменных и параметров. Стек логически разрастается и сужается при входе и выходе из функции: при входе (вызове) в стек добавляются параметры и локальные переменные, а при выходе они удаляются из стека.
Хип (куча) — блок памяти, в котором хранятся объекты, т.е. экземпляры ссылочных типов. Всякий раз при создании нового объекта он помещается в хип, и возвращается ссылка на объект. Во время выполнения программы хип заполняется по мере создания новых объектов. Сборщик мусора периодически удаляет объекты из хипа, чтобы избежать переполнения памяти. Объект подлежит удалению сразу после того, как на него перестает ссылаться хотя бы одна существующая переменная.
Экземпляры значимых типов и ссылки на объекты размещаются там где они были объявлены. Если экземпляр объявлен как свойство объекта или элемент массива, он храниться в хипе.
В C# нельзя явно удалить объект как это можно сделать в других языках. Вместо этого сборщик мусора удаляет объекты оставшиеся без ссылки.
В хипе также хранятся статические свойства и константы. Но в отличие от объектов они не уничтожаются сборщиком мусора, а продолжают существовать до завершения приложения.
Явное присвоение
C# реализует политику явного присвоения. Это означает, что:
- локальные переменные должны быть объявлены до использования
- при вызове метода (функции) ему должны быть переданы значения всех параметров
- всем остальным переменным (свойствам объектов, элементам массива) автоматически присваивается значение по умолчанию
Значение по умолчанию
Экземпляры всех классов имеют значение по умолчанию. Для предопределенных типов это результат бинарного обнуления памяти:
null
для ссылочных типов0
для числовых типов и типаenum
'\0'
для типаchar
false
для типаbool
С помощью ключевого слова default
можно задать значение по умолчанию для любого типа. Значение по умолчанию для пользовательских типов такое же как значение по умолчанию для каждого из его полей.
Параметры
Методы содержат ряд последовательных параметров. Параметры задают набор аргументов, которые должны быть переданы в метод при его вызове.
1 2 3 4 5 | static void Foo (int p) // p - параметр { ... } static void Main() { Foo (8); } // 8 - аргумент |
Передача по ссылке и по значению. Модификаторы ref и out
С помощью модификаторов ref
и out
можно задать как именно параметры должны передаваться в метод. По умолчанию аргументы передаются по значению. Это означает, что при передаче значения в метод в качестве аргумента создается копия этого значения, которая и передается в метод.
1 2 3 4 5 6 7 8 9 10 11 | static void Foo (int p) { p = p + 1; // Увеличиваем p на 1 Console.WriteLine (p); // Выводим p на экран } static void Main() { int x = 8; Foo (x); // Создается копия x Console.WriteLine (x); // x по прежнему равен 8 } |
Присвоение p
нового значения не изменяет значения x
, т.к. p
и x
хранятся в разных сегментах памяти.
При передаче аргумента ссылочного типа по значению копируется ссылка, а не сам объект.
1 2 3 4 5 6 7 8 9 10 11 | static void Foo (StringBuilder fooSB) { fooSB.Append ("test"); fooSB = null; } static void Main() { StringBuilder sb = new StringBuilder(); Foo (sb); Console.WriteLine (sb.ToString()); // test } |
В этом примере метод Foo
взаимодействует с тем же объектом, что и метод Main
, но с помощью разных ссылок на объект. Другими словами, sb
и fooSB
— разные переменные, ссылающиеся на один и тот же объект. Поскольку fooSB
— копия ссылки, присвоение ей значения null
не затрагивает переменную sb
(однако если бы fooSB
была объявлена с модификатором ref
, то sb
стала бы тоже равна null
).
Чтобы передать аргумент по ссылке нужно использовать модификатор параметра ref
.
1 2 3 4 5 6 7 8 9 10 11 | static void Foo (ref int p) { p = p + 1; Console.WriteLine (p); } static void Main() { int x = 8; Foo (ref x); // Передача x по ссылке Console.WriteLine (x); // x теперь равен 9 } |
В этом примере, т.к. p
и x
ссылаются на один и тот же сегмент памяти, присвоение нового значения p
изменяет значение x
.
Модификатор ref
нужно указывать два раза: при объявлении метода и при его вызове (при объявлении параметра и передаче аргумента). Параметр может быть передан по ссылке или по значению независимо от того, является ли тип параметра ссылочным или значимым типом.
Модификатор out
также как и ref
служит для передачи параметра по ссылке, но с двумя отличиями:
- аргумент, передаваемый по ссылке с использованием модификатора
out
, можно не инициализировать (не присваивать значения) до вызова метода - параметр, переданный с модификатором
out
, должен быть инициализирован (присвоено значение) до выхода из метода.
Модификатор out
часто используется, когда из метода необходимо вернуть несколько значений.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Test { static void Split (string name, out string firstNames, out string lastName) { int i = name.LastIndexOf (' '); firstNames = name.Substring (0, i); lastName = name.Substring (i + 1); } static void Main() { string a, b; Split ("Петров Иван Николаевич", out a, out b); Console.WriteLine (a); // Петров Иван Console.WriteLine (b); // Николаевич } } |
Модификатор params
Модификатор params
может быть указан для последнего параметра метода, что позволяет передать в метод любое число параметров одного и того же типа. Тип параметра при этом должен быть объявлен как массив.
1 2 3 4 5 6 | static int Sum (params int[] ints) { int sum = 0; for (int i = 0; i < ints.Length; i++) sum += ints[i]; return sum; } |
Передать аргументы при вызове метода можно двумя способами: как обычные аргументы через запятую или как один массив:
1 2 | Console.WriteLine (Sum (1, 2, 3, 4)); Console.WriteLine (new int[] { 1, 2, 3, 4 } ); |
Факультативные параметры
Начиная с версии C# 4.0 методы, конструкторы и индексаторы могут объявлять факультативные (optional) параметры. Параметр считается факультативным если при его объявлении указано значение по умолчанию. Факультативные параметры можно опустить при вызове метода.
1 2 | void Foo (int x = 23) { Console.WriteLine (x); } Foo(); // 23 |
При добавлении факультативного параметра в публичный метод, который вызывается из другой сборки, необходимо перекомпилировать обе сборки.
Значение по умолчанию факультативного параметра должно быть либо константой, либо не принимающим аргументов конструктором какого-либо из значимых типов. Факультативные параметры не могут передаваться по ссылке, соответственно их нельзя помечать модификатором ref
или out
. Обязательные параметры должны предшествовать факультативным и при объявлении метода и при его вызове. Исключение составляют параметры с модификатором params
— они всегда должны быть последними.
Именованные аргументы
Аргументы можно идентифицировать не только по позиции, но и по имени. Именованные аргументы могут следовать в любом порядке. Также можно смешивать именованные аргументы с обычными (позиционными), но именованные должны идти последними. Особенно удобно использовать именованные аргументы в случае факультативных параметров.
1 2 3 4 5 6 7 8 9 10 | void Foo (int x, int y) { Console.WriteLine (x + ", " + y); } void Test() { Foo (x:1, y:2); Foo (y:2, x:1); // В ином порядке - идентично предыдущему вызову Foo (1, y:2); // Микс из именованных и позиционных аргументов } |
Var — автоматическое определение типа
Часто переменные объявляются и инициализируются в один шаг. Если компилятор может предугадать тип переменной исходя из инициализирующего ее выражения, можно использовать ключевое слово var
вместо указания типа переменной. Несмотря на то, что тип переменной явно не указывается, ей нельзя в последующем присвоить значение другого типа.
1 2 | var x = 5; x = "hello"; // Ошибка компиляции |