Этот пример может не содержать для вас ничего нового, но важно понять, что .NET-структуры (как и перечни, которые будут рассмотрены в этой главе позже) тоже являются типами, характеризуемыми значением. Структуры, в частности, дают возможность использовать основные преимущества объектно-ориентированного подхода (инкапсуляции) при сохранении эффективности размещения данных в стеке. Подобно классам, структуры могут использовать конструкторы (с аргументами) и определять любое число членов.
Все структуры неявно получаются из класса System.ValueType. С точки зрения функциональности, единственной целью System.ValueType является "переопределение" виртуальных методов System.Object (этот объект будет описан чуть позже) с целью учета особенностей семантики типов, заданных значениями, в противоположность ссылочным типам. Методы экземпляра, определенные с помощью System.ValueType, будут идентичны соответствующим методам System.Object.
// Структуры и перечни являются расширениями System.ValueType.
public abstract class ValueType: object {
public virtual bool Equals(object obj);
public virtual int GetHashCode();
public Type GetType();
public virtual string ToString();
}
Предположим, что вы создали C#-структуру с именем MyPoint, используя ключевое слово C# struct.
// Структуры являются типами, которые характеризуются значениями.
struct MyPoint {
public int x, у;
}
Чтобы разместить в памяти тип структуры, можно использовать ключевое слово new, что, кажется, противоречит интуиции, поскольку обычно подразумевается, что new всегда размещает данные в динамически распределяемой памяти. Это частица общего "тумана", сопровождающего CLR. Мы можем полагать, что вообще все в программе является объектами и значениями, создаваемыми с помощью new. Однако в том случае, когда среда выполнения обнаруживает тип. полученный из System.ValueType, выполняется обращение к стеку.
// Все равно используется стек!
MyPoint р = new MyPoint();
Структуры могут создаваться и без использования ключевою слова new.
MyPoint p1;
p1.x = 100;
p1.y = 100;
Но при использовании этого подхода вы
Типы, характеризуемые значениями, ссылочные типы и оператор присваивания
Теперь изучите следующий метод Main() и рассмотрите его вывод, показанный на рис. 3.12.
static void Main(string[] args) {
Console.WriteLine("*** Типы, характеризуемые значением / Ссылочные типы ***");
Console.WriteLine(''-› Создание p1");
MyPoint p1 = new MyPoint();
p1.x = 100;
p1.у = 100;
Console.WriteLine("-› Приcваивание p1 типу p2\n");
MyPoint p2 = p1;
// Это p1.
Console.WriteLine"p1.x = {0}
Console.WriteLine"p1.y = {0}
// Это р2.
Console.WriteLine("p2.x = {0}", p2.x);
Console.WriteLine("p2.у = {0}", p2.y);
// Изменение p2.x. Это НЕ влияет на p1.x.
Console.WriteLine("-› Замена значения p2.x на 900");
р2.х = 900;
// Новая печать.
Console.WriteLine("-› Это снова значения х… ");
Console.WriteLine("p1.x = {0}", p1.x);
Console.WriteLine("p2.x = {0}", р2.х);
Console ReadLine();
}
Рис. 3.12. Для типов, характеризуемых значениями, присваивание означает буквальное копирование каждого поля
Здесь создается переменная типа MyPoint (с именем p1), которая затем присваивается другой переменной типа MyPoint (р2). Ввиду того, что MyPoint является типом, характеризуемым значением, в результате в стеке будет две копии типа MyPoint, каждая из которых может обрабатываться независимо одна от другой. Поэтому, когда изменяется значение р2.х, значение p1.x остается прежним (точно так же, как в предыдущем примере с целочисленными данными).