В заключение этого раздела приведу таблицу (3.1), содержащую описание всех встроенных типов языка C# и их основные характеристики.
Система встроенных типов языка C# не только содержит практически все встроенные типы (за исключением long double) стандарта языка C++, но и перекрывает его разумным образом. В частности тип string является встроенным в язык, что вполне естественно. В области совпадения сохранены имена типов, принятые в C++, что облегчает жизнь тем, кто привык работать на C++, но собирается по тем или иным причинам перейти на язык С#.
Типы или классы? И типы, и классы
Язык C# в большей степени, чем язык C++, является языком объектного программирования. В чем это выражается? В языке C# сглажено различие между типом и классом. Все типы — встроенные и пользовательские — одновременно являются классами, связанными отношением наследования. Родительским, базовым классом является класс Object. Все остальные типы или, точнее, классы являются его потомками, наследуя методы этого класса. У класса Object есть четыре наследуемых метода:
1. bool Equals (object obj) — проверяет эквивалентность текущего объекта и объекта, переданного в качестве аргумента;
2. System.туре GetType () — возвращает системный тип текущего объекта;
3. string Tostring () — возвращает строку, связанную с объектом. Для арифметических типов возвращается значение, преобразованное в строку;
4. int GetHashCode () — служит как хэш-функция в соответствующих алгоритмах поиска по ключу при хранении данных в хэш-таблицах.
Естественно, что все встроенные типы нужным образом переопределяют методы родителя и добавляют собственные методы и свойства. Учитывая, что и типы, создаваемые пользователем, также являются потомками класса Object, то для них необходимо переопределить методы родителя, если предполагается использование этих методов-, реализация родителя, предоставляемая по умолчанию, не обеспечивает нужного эффекта.
Перейдем теперь к примерам, на которых будем объяснять дальнейшие вопросы, связанные с типами и классами, переменными и объектами. Начнем с вполне корректного в языке C# примера объявления переменных и присваивания им значений:
int x=11;
int v = new Int32();
v = 007;
string s1 = "Agent";
s1 = s1 + v.ToString() +x.ToString();
В этом примере переменная х объявляется как обычная переменная типа int. в то же время для объявления переменной v того же типа int используется стиль, принятый для объектов. В объявлении применяется конструкция new и вызов конструктора класса. В операторе присваивания, записанном в последней строке фрагмента, для обеих переменных вызывается метод ToString, как это делается при работе с объектами. Этот метод, наследуемый от родительского класса Object, переопределенный в классе int, возвращает строку с записью целого. Сообщу еще, что класс int не только наследует методы родителя — класса Object, — но и дополнительно определяет метод СоmраreTо, выполняющий сравнение целых, и метод GetTypeCode, возвращающий системный код типа. Для класса int определены также статические методы и поля, о которых расскажу чуть позже.
Так что же такое после этого int, спросите Вы: тип или класс? Ведь ранее говорилось, что int относится к value-типам, следовательно, он хранит в стеке значения своих переменных, в то время как объекты должны задаваться ссылками. С другой стороны, создание экземпляра с помощью конструктора, вызов методов, наконец, существование родительского класса Object, — все это указывает на то, что int — это настоящий класс. Правильный ответ состоит в том, что int — это и тип, и класс. В зависимости от контекста х может восприниматься как переменная типа int или как объект класса int. Это же верно и для всех остальных value-типов. Замечу еще, что все значимые типы фактически реализованы как структуры, представляющие частный случай класса.
Остается понять, для чего в языке C# введена такая двойственность. Для int и других значимых типов сохранена концепция типа не только из-за ностальгических воспоминаний о типах. Дело в том, что значимые типы эффективнее в реализации, им проще отводить память, так что именно соображения эффективности реализации заставили авторов языка сохранить значимые типы. Более важно, что зачастую необходимо оперировать значениями, а не ссылками на них, хотя бы из-за различий в семантике присваивания для переменных ссылочных и значимых типов.
С другой стороны, в определенном контексте крайне полезно рассматривать переменные типа int как настоящие объекты и обращаться с ними как с объектами. В частности, полезно иметь возможность создавать и работать со списками, чьи элементы являются разнородными объектами, в том числе и принадлежащими к значимым типам.
Дальнейшие примеры работы с типами и проект Types
Обсуждение особенностей тех или иных конструкций языка невозможно без приведения примеров. Для каждой лекции я строю один или несколько проектов, сохраняя по возможности одну и ту же схему и реально выполняя проекты в среде Visual Studio.Net. Для работы с примерами данной лекции построен консольный проект с именем Types, содержащий два класса: Class1 и Testing. Расскажу чуть подробнее о той схеме, по которой выстраиваются проекты. Класс Class1 строится автоматически при начальном создании проекта. Он содержит процедуру Main — точку входа в проект. В процедуре Main создается объект класса Testing и вызываются методы этого класса, тестирующие те или иные ситуации. Для решения специальных задач, помимо всегда создаваемого класса Testing, создаются один или несколько классов. Добавление нового класса в проект я осуществляю выбором пункта меню Project/Add Class. В этом случае автоматически строится заготовка для нового класса, содержащая конструктор без параметров. Дальнейшая работа над классом ведется над этой заготовкой.
Создаваемые таким образом классы хранятся в проекте в отдельных файлах. Это особенно удобно, если классы используются в разных проектах. Функционально связанную группу классов удобнее хранить в одном файле, что не возбраняется.
Все проекты в книге являются самодокументируемыми. Классы и их методы сопровождаются тегами <summary>. В результате появляются подсказки при вызове методов и возможность построения XML-отчета, играющего роль спецификации проекта.
Приведу текст класса Class1;
using System;
namespace Types
{
/// <summary>
/// Проект Types содержит примеры, иллюстрирующие работу
/// со встроенными скалярными типами языка С#.
/// Проект содержит классы: Testing, Class1.
///
/// </summary>
class Class1
{
/// <summary>
/// Точка входа проекта.
/// В ней создается объект класса Testing
/// и вызываются его методы.
/// </summary>
[STAThread]
static void Main()
{
Testing tm = new Testing ();
Console.WriteLine("Testing.Who Test");
tm.WhoTest();
Console.WriteLine("Testing.Back Test");
tm.BackTest ();
Console.WriteLine("Testing.OLoad Test");
tm.OLoadTest ();
Console.WriteLine("Testing.ToString Test");
tm.ToStringTest ();