Ниже приведен пример объявления интерфейса для класса, генерирующего последовательный ряд чисел.
public interface ISeries {
int GetNextO; // возвратить следующее по порядку число
void Reset(); // перезапустить
void SetStart(int x); // задать начальное значение
}
Этому интерфейсу присваивается имя ISeries. Префикс I в имени интерфейса указывать необязательно, но это принято делать в практике программирования, чтобы как-то отличать интерфейсы от классов. Интерфейс ISeries объявляется как public и поэтому может быть реализован в любом классе какой угодно программы.
Помимо методов, в интерфейсах можно также указывать свойства и индексаторы. Интерфейсы не могут содержать члены данных. В них нельзя также определить конструкторы, деструкторы или операторные методы. Кроме того, ни один из членов интерфейса не может быть объявлен как static.
Как только интерфейс будет определен, он может быть реализован в одном или нескольких классах. Для реализации интерфейса достаточно указать его имя после имени класса, аналогично базовому классу. Ниже приведена общая форма реализации интерфейса в классе.
class имя_класса : имя_интерфейса {
// тело класса
}
где имя_интерфейса — это конкретное имя реализуемого интерфейса. Если интерфейс реализуется в классе, то это должно быть сделано полностью. Реализовать интерфейс выборочно и только по частям нельзя.
В классе допускается реализовывать несколько интерфейсов. В этом случае все реализуемые в классе интерфейсы указываются списком через запятую. В классе можно наследовать базовый класс и в тоже время реализовать один или более интерфейс. В таком случае имя базового класса должно быть указано перед списком интерфейсов, разделяемых запятой.
Методы, реализующие интерфейс, должны быть объявлены как public. Дело в том, что в самом интерфейсе эти методы неявно подразумеваются как открытые, поэтому их реализация также должна быть открытой. Кроме того, возвращаемый тип и сигнатура реализуемого метода должны точно соответствовать возвращаемому типу и сигнатуре, указанным в определении интерфейса.
Ниже приведен пример программы, в которой реализуется представленный ранее интерфейс ISeries. В этой программе создается класс ByTwo, генерирующий последовательный ряд чисел, в котором каждое последующее число на два больше предыдущего.
// Реализовать интерфейс ISeries,
class ByTwos : ISeries {
int start;
int val;
public ByTwos () {
start = 0;
val = 0;
}
public int GetNext() {
val += 2;
return val;
}
public void Reset() {
val = start;
}
public void SetStart(int x) {
start = x;
val = start;
}
}
В С# допускается объявлять переменные ссылочного интерфейсного типа, т.е. переменные ссылки на интерфейс. Такая переменная может ссылаться на любой объект, реализующий ее интерфейс. При вызове метода для объекта посредством интерфейсной ссылки выполняется его вариант, реализованный в классе данного объекта. Переменной ссылки на интерфейс доступны только методы, объявленные в ее интерфейсе. Поэтому интерфейсную ссылку нельзя использовать для доступа к любым другим переменным и методам, которые не поддерживаются объектом класса, реализующего данный интерфейс.
Аналогично методам, свойства указываются в интерфейсе вообще без тела. Ниже приведена общая форма объявления интерфейсного свойства.
// Интерфейсное свойство
тип имя{
get;
set;
}
Очевидно, что в определении интерфейсных свойств, доступных только для чтения или только для записи, должен присутствовать единственный аксессор: get или set соответственно.
Несмотря на то что объявление свойства в интерфейсе очень похоже на объявление автоматически реализуемого свойства в классе, между ними все же имеется отличие. При объявлении в интерфейсе свойство не становится автоматически реализуемым. В этом случае указывается только имя и тип свойства, а его реализация предоставляется каждому реализующему классу. Кроме того, при объявлении свойства в интерфейсе не разрешается указывать модификаторы доступа для аксессоров.
В интерфейсе можно также указывать индексаторы. Ниже приведена общая форма объявления интерфейсного индексатора.
// Интерфейсный индексатор
тип_элемента this[int индекс]{
get;
set;
}
Как и прежде, в объявлении интерфейсных индексаторов, доступных только для чтения или только для записи, должен присутствовать единственный аксессор: get или set соответственно.
Один интерфейс может наследовать другой. Синтаксис наследования интерфейсов такой же, как и у классов. Когда в классе реализуется один интерфейс, наследующий другой, в нем должны быть реализованы все члены, определенные в цепочке наследования интерфейсов.
Как и в любом языке программирования, в С# поставляется собственный набор основных типов данных, которые должны применяться для представления локальных переменных, переменных экземпляра, возвращаемых значений и входных параметров. Однако в отличие от других языков программирования, в С# эти ключевые слова представляют собой нечто большее, чем просто распознаваемые компилятором лексемы. Они, по сути, представляют собой сокращенные варианты обозначения полноценных типов из пространства имен System. В табл. 3 0перечислены эти системные типы данных вместе с охватываемыми ими диапазонами значений, соответствующими ключевыми словами на С# и сведениями о том, отвечают ли они требованиям общеязыковой спецификации CLS (Common Language Specification).
Таблица 3
Системные типы данных в С#
Cокращенный вариант обозначения в С# |
Отвечает ли требованиям CLS |
Системный тип |
Диапазон значений |
Описание |
bool |
Да |
System.Boolean |
true или false |
Представляет признак истиности или ложности |
sbyte |
Нет |
System.SByte |
от -127 до 128 |
8-битное число со знаком |
byte |
Да |
System.Byte |
от 0 до 255 |
8-битное число без знака |
short |
Да |
System.Int16 |
от -32 768 до 32767 |
16-битное число со знаком |
ushort |
Нет |
System.UInt16 |
от 0 до 65 535 |
16-битное число без знака |
int |
Да |
System.Int32 |
от - 2 147 483 648 до 2 147 483 647 |
32-битное число со знаком |
uint |
Нет |
System.UInt32 |
от 0 до 4 294 967 295 |
32-битное число без знака |
long |
Да |
System.Int64 |
от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 |
64-битное число со знаком |
ulong |
Нет |
System.UInt64 |
от 0 до 18 446 744 073 709 551 615 |
64-битное число без знака |
char |
Да |
System.Char |
от U+0000 до U+ffff |
Одиночный 16-битный символ Unicode |
float |
Да |
System.Single |
от +1,5×10-45 до 3,4×1038 |
32-битное число с плавающей точкой |
double |
Да |
System.Double |
от 5,0×10-324 до 1,7×10308 |
64-битное число с плавающей точкой |
decimal |
Да |
System.Decimal |
от ±1,0×10e-28 до ±7,9×1028 |
96-битное число с плавающей точкой |
string |
Да |
System.String |
Ограничивается объемом системной памяти |
Представляет ряд символов в формате Unicode |
object |
Да |
System.Object |
Позволяет сохранять любой тип в объектной переменной |
Служит базовым классом для всех типов в мире .NET |
Интересно отметить то, что даже элементарные типы данных в .NET имеют вид иерархии классов. Типы, которые находятся в самом верху иерархии, обеспечивают некоторое поведение по умолчанию, которое передается унаследованным от них типам. На рис. 1 схематично показаны отношения между ключевыми системными типами.
Р
ис.
1. Иерархия классов системных типов
Обратите внимание, что каждый из этих типов в конечном итоге наследуется от класса System.Object, в котором содержится набор методов (таких как ToString (), Equals () и GetHashCode ()), являющихся общими для всех поставляемых в библиотеках базовых классов .NET типов.
В С# имеются две общие категории встроенных типов данных: типы значений и ссылочные типы. Они отличаются по содержимому переменной. Если переменная относится к типу значения, то она содержит само значение, например 3,1416 или 212. А если переменная относится к ссылочному типу, то она содержит ссылку на значение. Наиболее распространенным примером использования ссылочного типа является класс.
Каждый из числовых типов, такой как short или int. отображается на соответствующую структуру в пространстве имен System. Структуры представляют собой типы значений, которые размещаются в стеке. Типы string и object, с другой стороны, являются ссылочными типами, а это значит, что данные, сохраняемые в переменных такого типа, размещаются в управляемой куче.
Стек — это структура данных, которая сохраняет элементы по принципу: первым пришел, последним ушел. Стек относится к области памяти, поддерживаемой процессором, в которой сохраняются локальные переменные. Доступ к стеку во много раз быстрее, чем к общей области памяти, поэтому использование стека для хранения данных ускоряет работу вашей программы. В С# размерные типы (например, целые числа) располагаются в стеке: для их значений зарезервирована область в стеке, и доступ к ней осуществляется по названию переменной.
Ссылочные типы (например, объекты) располагаются в куче. Куча — это оперативная память компьютера. Доступ к ней осуществляется медленнее, чем к стеку. Когда объект располагается в куче, то переменная хранит лишь адрес объекта. Этот адрес хранится в стеке. По адресу программа имеет доступ к самому объекту, все данные которого сохраняются в общем куске памяти (куче).
Важно отметить, что многие из числовых типов данных унаследованы от класса System.ValueType. Потомки ValueType автоматически размещаются в стеке и потому обладают очень предсказуемым временем жизни и являются довольно эффективными. Типы, у которых в цепочке наследования не присутствует класс System.ValueType (вроде System.Type, System.String, System.Array, System.Exception и System. Delegate), в стеке не размещаются, а попадают в кучу и подвергаются автоматической сборке мусора.
Роль класса System.ValueType заключается в гарантировании размещения производного типа (например, любой структуры) в стеке, а не в куче с автоматически производимой сборкой мусора. Данные, размещаемые в стеке, могут создаваться и уничтожаться очень быстро, поскольку срок их жизни зависит только от контекста, в котором они определены. За данными, размещаемыми в куче, наблюдает сборщик мусора .NET, и время их существования зависит от целого ряда различных факторов.
С функциональной точки зрения единственной задачей System.ValueType является переопределение виртуальных методов, объявленных в System.Object, так, чтобы в них использовалась семантика, основанная на значениях, а не на ссылках. Под переопределением понимается изменение реализации виртуального (или, что тоже возможно, абстрактного) метода, определенного внутри базового класса. Базовым классом для 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(); }
Из-за того, что в типах значения используется семантика, основанная на значениях, время жизни структуры (которая включает все числовые типы данных, наподобие int, float и т.д., а также любое перечисление или специальную структуру) получается очень предсказуемым. При выходе переменной типа структуры за пределы контекста, в котором она определялась, она сразу же удаляется из памяти.
// Локальные структуры извлекаются из стека // после завершения метода. static void LocalValueTypes () { //В действительности int представляет // собой структуру System.Int32. int i = 0; //В действительности Point представляет // собой тип структуры. Point p = new Point () ; } // Здесь i и р изымаются из стека.