static void SendAPersonByReference(ref Person p)
{
// Изменить некоторые данные в р.
p.personAge = 555;
// р теперь указывает на новый объект в куче!
p = new Person("Nikki", 999);
}
Как и можно было ожидать, вызываемому коду предоставлена полная свобода в плане манипулирования входным параметром. Вызываемый код может не только изменять состояние объекта, но и переопределять ссылку так, чтобы она указывала на новый объект Person. Взгляните на следующий обновленный код:
// Передача ссылочных типов по ссылке.
Console.WriteLine("***** Passing Person object by reference *****");
...
Person mel = new Person("Mel", 23);
Console.WriteLine("Before by ref call, Person is:");
// Перед вызовом с передачей по ссылке
mel.Display();
SendAPersonByReference(ref mel);
Console.WriteLine("After by ref call, Person is:");
// После вызова с передачей по ссылке
mel.Display();
Console.ReadLine();
Вот вывод:
***** Passing Person object by reference *****
Before by ref call, Person is:
Name: Mel, Age: 23
After by ref call, Person is:
Name: Nikki, Age: 999
Здесь видно, что после вызова объект по имени Mel возвращается как объект по имени Nikki, поскольку метод имел возможность изменить то, на что указывала в памяти входная ссылка. Ниже представлены основные правила, которые необходимо соблюдать при передаче ссылочных типов.
• Если ссылочный тип передается по ссылке, тогда вызываемый код может изменять значения данных состояния объекта, а также объект, на который указывает ссылка.
• Если ссылочный тип передается по значению, то вызываемый код может изменять значения данных состояния объекта, но не объект, на который указывает ссылка.
Заключительные детали относительно типов значений и ссылочных типов
В завершение данной темы в табл. 4.4 приведена сводка по основным отличиям между типами значений и ссылочными типами.
Несмотря на различия, типы значений и ссылочные типы могут реализовывать интерфейсы и поддерживать любое количество полей, методов, перегруженных операций, констант, свойств и событий.
Понятие типов С#, допускающих null
Давайте исследуем роль типов данных, допускающих значение null, с применением проекта консольного приложения по имени FunWithNullableValueTypes. Как вам уже известно, типы данных C# обладают фиксированным диапазоном значений и представлены в виде типов пространства имен System. Например, тип данных System.Boolean может принимать только значения из набора (true, false). Вспомните, что все числовые типы данных (а также Boolean) являются типами значений. Типам значений никогда не может быть присвоено значение null, потому что оно служит для представления пустой объектной ссылки.
(window.adrunTag = window.adrunTag || []).push({v: 1, el: 'adrun-4-390', c: 4, b: 390})
// Ошибка на этапе компиляции!
// Типы значений нельзя устанавливать в null!
bool myBool = null;
int myInt = null;
В языке C# поддерживается концепция типов данных, допускающих значение null. Выражаясь просто, допускающий null тип может представлять все значения лежащего в основе типа плюс null. Таким образом, если вы объявите переменную типа bool, допускающего null, то ей можно будет присваивать значение из набора {true, false, null}. Это может быть чрезвычайно удобно при работе с реляционными базами данных, поскольку в таблицах баз данных довольно часто встречаются столбцы, для которых значения не определены. Без концепции типов данных, допускающих null, в C# не было бы удобного способа для представления числовых элементов данных без значений.
Чтобы определить переменную типа, допускающего null, необходимо добавить к имени интересующего типа данных суффикс в виде знака вопроса (?). До выхода версии C# 8.0 такой синтаксис был законным только в случае применения к типам значений (более подробные сведения ищите в разделе "Использование ссылочных типов, допускающих null" далее в главе). Подобно переменным с типами, не допускающими null, локальным переменным, имеющим типы, которые допускают null, должно присваиваться начальное значение, прежде чем ими можно будет пользоваться:
static void LocalNullableVariables()
{
// Определить несколько локальных переменных
// с типами, допускающими null
int? nullableInt = 10;
double? nullableDouble = 3.14;
bool? nullableBool = null;
char? nullableChar = 'a';
int?[] arrayOfNullableInts = new int?[10];
}
Использование типов значений, допускающих null
В языке C# система обозначений в форме суффикса ? представляет собой сокращение для создания экземпляра обобщенного типа структуры System.Nullable<T>. Она также применяется для создания ссылочных типов, допускающих null, но ее поведение несколько отличается. Хотя подробное исследование обобщений мы отложим до главы 10, сейчас важно понимать, что тип System.Nullable<T> предоставляет набор членов, которые могут применяться всеми типами, допускающими null.