// Присваивание двух внутренних типов значений дает
// в результате две независимые переменные в стеке.
static void ValueTypeAssignment
{
Console.WriteLine("Assigning value types\n");
Point p1 = new Point(10, 10);
Point p2 = p1;
// Вывести значения обеих переменных Point.
p1.Display;
p2.Display;
// Изменить pl.X и снова вывести значения переменных.
//
Значение р2.Х не изменилось.
p1.X = 100;
Console.WriteLine("\n=> Changed p1.X\n");
p1.Display;
p2.Display;
}
Здесь создается переменная типа
Point(p1)
, которая присваивается другой переменной типа
Point(р2)
. Поскольку
Point
— тип значения, в стеке находятся две копии
Point
, каждой из которых можно манипулировать независимым образом. Поэтому при изменении значения
p1.X
значение
р2.X
остается незатронутым:
Assigning value types
X = 10, Y = 10
X = 10, Y = 10
=> Changed p1.X
X = 100, Y = 10
X = 10, Y = 10
По контрасту с типами значений, когда операция присваивания применяется к переменным ссылочных типов (т.е. экземплярам всех классов), происходит перенаправление на то, на что ссылочная переменная указывает в памяти. В целях иллюстрации создайте новый класс по имени
PointRef
с теми же членами, что и у структуры
Point
, но только переименуйте конструктор в соответствии с именем данного класса:
// Классы всегда являются ссылочными типами.
class PointRef
{
// Те же самые члены, что и в структуре Point...
// Не забудьте изменить имя конструктора на PointRef!
public PointRef(int xPos, int yPos)
{
X = xPos;
Y = yPos;
}
}
Задействуйте готовый тип
PointRef
в следующем новом методе. Обратите внимание, что помимо использования класса
PointRef
вместо структуры
Point
код идентичен коду метода
ValueTypeAssignment
:
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)
Здесь ссылочный тип содержится внутри типа значения. Возникает важный вопрос: что произойдет в результате присваивания одной переменной типа
Rectangle
другой переменной того же типа? Учитывая то, что уже известно о типах значений, можно корректно предположить, что целочисленные данные (которые на самом деле являются структурой —
System.Int32
)должны быть независимой сущностью для каждой переменной
Rectangle
. Но что можно сказать о внутреннем ссылочном типе? Будет ли полностью скопировано состояние этого объекта или же только ссылка на него? Чтобы получить ответ, определите следующий метод и вызовите его: