static void ReferenceTypeAssignment()
{
Console.WriteLine("Assigning reference types\n");
PointRef p1 = new PointRef(10, 10);
PointRef p2 = p1;
// Вывести значения обеих переменных PointRef.
p1.Display();
p2.Display();
// Изменить pl.X и снова вывести значения.
p1.X = 100;
Console.WriteLine("\n=> Changed p1.X\n");
p1.Display();
p2.Display();
}
В рассматриваемом случае есть две ссылки, указывающие на тот же самый объект в управляемой куче. Таким образом, когда значение X изменяется с использованием ссылки p1, изменится также и значение р2.X. Вот вывод, получаемый в результате вызова этого нового метода:
Assigning reference types
X = 10, Y = 10
X = 10, Y = 10
=> Changed p1.X
X = 100, Y = 10
X = 100, Y = 10
Использование типов значений, содержащих ссылочные типы
Теперь, когда вы лучше понимаете базовые отличия между типами значений и ссылочными типами, давайте обратимся к более сложному примеру. Предположим, что имеется следующий ссылочный тип (класс), который поддерживает информационную строку (InfoString), устанавливаемую с применением специального конструктора:
class ShapeInfo
{
public string InfoString;
public ShapeInfo(string info)
{
InfoString = info;
}
}
Далее представим, что переменная типа ShapeInfo должна содержаться внутри типа значения по имени Rectangle. Кроме того, в типе Rectangle предусмотрен специальный конструктор, который позволяет вызывающему коду указывать значение для внутренней переменной-члена типа ShapeInfo. Вот полное определение типа Rectangle:
struct Rectangle
{
// Структура Rectangle содержит член ссылочного типа.
public ShapeInfo RectInfo;
public int RectTop, RectLeft, RectBottom, RectRight;
public Rectangle(string info, int top, int left, int bottom, int right)
{
RectInfo = new ShapeInfo(info);
RectTop = top; RectBottom = bottom;
RectLeft = left; RectRight = right;
}
public void Display()
{
Console.WriteLine("String = {0}, Top = {1}, Bottom = {2}, " +
"Left = {3}, Right = {4}",
RectInfo.InfoString, RectTop, RectBottom, RectLeft, RectRight);
}
}
Здесь ссылочный тип содержится внутри типа значения. Возникает важный вопрос: что произойдет в результате присваивания одной переменной типа Rectangle другой переменной того же типа? Учитывая то, что уже известно о типах значений, можно корректно предположить, что целочисленные данные (которые на самом деле являются структурой — System.Int32)должны быть независимой сущностью для каждой переменной Rectangle. Но что можно сказать о внутреннем ссылочном типе? Будет ли полностью скопировано состояние этого объекта или же только ссылка на него? Чтобы получить ответ, определите следующий метод и вызовите его:
static void ValueTypeContainingRefType()
{
// Создать первую переменную Rectangle.
Console.WriteLine("-> Creating r1");
Rectangle r1 = new Rectangle("First Rect", 10, 10, 50, 50);
// Присвоить новой переменной Rectangle переменную r1.
Console.WriteLine("-> Assigning r2 to r1");
Rectangle r2 = r1;
// Изменить некоторые значения в r2.
Console.WriteLine("-> Changing values of r2");
r2.RectInfo.InfoString = "This is new info!";
r2.RectBottom = 4444;
// Вывести значения из обеих переменных Rectangle.
r1.Display();
r2.Display();
}
Вывод будет таким:
-> Creating r1
-> Assigning r2 to r1
-> Changing values of r2
String = This is new info!, Top = 10, Bottom = 50, Left = 10, Right = 50
String = This is new info!, Top = 10, Bottom = 4444, Left = 10, Right = 50
Как видите, в случае модификации значения информационной строки с использованием ссылки r2 для ссылки r1 отображается то же самое значение. По умолчанию, если тип значения содержит другие ссылочные типы, то присваивание приводит к копированию ссылок. В результате получаются две независимые структуры, каждая из которых содержит ссылку, указывающую на один и тот же объект в памяти (т.е. создается поверхностная копия). Для выполнения глубокого копирования, при котором в новый объект полностью копируется состояние внутренних ссылок, можно реализовать интерфейс ICloneable (что будет показано в главе 8).