Текст книги "Полное руководство. С# 4.0"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 23 (всего у книги 58 страниц)
В методе Main() переменная ob объявляется для ссылки на интерфейс ISeries. Это означает, что в ней могут храниться ссылки на объект любого класса, реализующе го интерфейс ISeries. В данном случае она служит для ссылки на объекты twoOb и primeOb классов ByTwos и Primes соответственно, в которых реализован интерфейс ISeries.
И еще одно замечание: переменной ссылки на интерфейс доступны только методы, объявленные в ее интерфейсе. Поэтому интерфейсную ссылку нельзя использовать для доступа к любым другим переменным и методам, которые не поддерживаются объек том класса, реализующего данный интерфейс. Интерфейсные свойства
Аналогично методам, свойства указываются в интерфейсе вообще без тела. Ниже приведена общая форма объявления интерфейсного свойства. // Интерфейсное свойство тип имя{ get; set; }
Очевидно, что в определении интерфейсных свойств, доступных только для чтения или только для записи, должен присутствовать единственный аксессор: get или set соответственно.
Несмотря на то что объявление свойства в интерфейсе очень похоже на объявление автоматически реализуемого свойства в классе, между ними все же имеется отличие. При объявлении в интерфейсе свойство не становится автоматически реализуемым. В этом случае указывается только имя и тип свойства, а его реализация предоставляет ся каждому реализующему классу. Кроме того, при объявлении свойства в интерфейсе не разрешается указывать модификаторы доступа для аксессоров. Например, аксессор set не может быть указан в интерфейсе как private.
Ниже в качестве примера приведен переделанный вариант интерфейса ISeries и класса ByTwos, в котором свойство Next используется для получения и установки следующего по порядку числа, которое больше предыдущего на два. // Использовать свойство в интерфейсе. using System; public interface ISeries { // Интерфейсное свойство. int Next { get; // возвратить следующее по порядку число set; // установить следующее число } } // Реализовать интерфейс ISeries. class ByTwos : ISeries { int val; public ByTwos() { val = 0; } // Получить или установить значение. public int Next { get { val += 2; return val; } set { val = value; } } } // Продемонстрировать применение интерфейсного свойства. class SeriesDemo3 { static void Main() { ByTwos ob = new ByTwos(); // Получить доступ к последовательному ряду чисел с помощью свойства. for(int i=0; i < 5; i++) Console.WriteLine("Следующее число равно " + ob.Next); Console.WriteLine("nНачать с числа 21"); ob.Next = 21; for(int i=0; i < 5; i++) Console.WriteLine("Следующее число равно " + ob.Next); } }
При выполнении этого кода получается следующий результат. Следующее число равно 2 Следующее число равно 4 Следующее число равно 6 Следующее число равно 8 Следующее число равно 10 Начать с числа 21 Следующее число равно 23 Следующее число равно 25 Следующее число равно 27 Следующее число равно 29 Следующее число равно 31 Интерфейсные индексаторы
В интерфейсе можно также указывать индексаторы. Ниже приведена общая форма объявления интерфейсного индексатора. // Интерфейсный индексатор тип_элемента this[int индекс]{ get; set; }
Как и прежде, в объявлении интерфейсных индексаторов, доступных только для чтения или только для записи, должен присутствовать единственный аксессор: get или set соответственно.
Ниже в качестве примера приведен еще один вариант реализации интерфейса ISeries, в котором добавлен индексатор только для чтения, возвращающий i-й эле мент числового ряда. // Добавить индексатор в интерфейс. using System; public interface ISeries { // Интерфейсное свойство. int Next { get; // возвратить следующее по порядку число set; // установить следующее число } // Интерфейсный индексатор. int this[int index] { get; // возвратить указанное в ряду число } } // Реализовать интерфейс ISeries. class ByTwos : ISeries { int val; public ByTwos() { val = 0; } // Получить или установить значение с помощью свойства. public int Next { get { val += 2; return val; } set { val = value; } } // Получить значение по индексу. public int this[int index] { get { val = 0; for(int i=0; i < index; i++) val += 2; return val; } } } // Продемонстрировать применение интерфейсного индексатора. class SeriesDemo4 { static void Main() { ByTwos ob = new ByTwos(); // Получить доступ к последовательному ряду чисел с помощью свойства. for(int i=0; i < 5; i++) Console.WriteLine("Следующее число равно " + ob.Next); Console.WriteLine("nНачать с числа 21"); ob.Next = 21; for (int i=0; i < 5; i++) Console.WriteLine("Следующее число равно " + ob.Next); Console.WriteLine("nСбросить в 0"); ob.Next = 0; // Получить доступ к последовательному ряду чисел с помощью индексатора for(int i=0; i < 5; i++) Console.WriteLine("Следующее число равно " + ob[i]); } }
Вот к какому результату приводит выполнение этого кода. Следующее число равно 2 Следующее число равно 4 Следующее число равно 6 Следующее число равно 8 Следующее число равно 10 Начать с числа 21 Следующее число равно 23 Следующее число равно 25 Следующее число равно 27 Следующее число равно 29 Следующее число равно 31 Сбросить в 0 Следующее число равно 0 Следующее число равно 2 Следующее число равно 4 Следующее число равно 6 Следующее число равно 8 Наследование интерфейсов
Один интерфейс может наследовать другой. Синтаксис наследования интерфейсов такой же, как и у классов. Когда в классе реализуется один интерфейс, наследующий другой, в нем должны быть реализованы все члены, определенные в цепочке наследо вания интерфейсов, как в приведенном ниже примере. // Пример наследования интерфейсов. using System; public interface IA { void Meth1(); void Meth2(); } // В базовый интерфейс включены методы Meth1() и Meth2(). // а в производный интерфейс добавлен еще один метод – Meth3(). public interface IB : IA { void Meth3(); } // В этом классе должны быть реализованы все методы интерфейсов IA и IB. class MyClass : IB { public void Methl() { Console.WriteLine("Реализовать метод Meth1()."); } public void Meth2() { Console.WriteLine("Реализовать метод Meth2()."); } public void Meth3() { Console.WriteLine("Реализовать метод Meth3()."); } } class IFExtend { static void Main() { MyClass ob = new MyClass(); ob.Meth1(); ob.Meth2(); ob.Meth3(); } }
Ради интереса попробуйте удалить реализацию метода Meth1() из класса MyClass. Это приведет к ошибке во время компиляции. Как пояснялось ранее, в любом классе, реализующем интерфейс, должны быть реализованы все методы, определенные в этом интерфейсе, в том числе и те, что наследуются из других интерфейсов. Сокрытие имен при наследовании интерфейсов
Когда один интерфейс наследует другой, то в производном интерфейсе может быть объявлен член, скрывающий член с аналогичным именем в базовом интерфейсе. Такое сокрытие имен происходит в том случае, если член в производном интерфей се объявляется таким же образом, как и в базовом интерфейсе. Но если не указать в объявлении члена производного интерфейса ключевое слово new, то компилятор вы даст соответствующее предупреждающее сообщение. Явные реализации
При реализации члена интерфейса имеется возможность указать его имя полно стью вместе с именем самого интерфейса. В этом случае получается явная реализация члена интерфейса, или просто явная реализация. Так, если объявлен интерфейс IMyIF interface IMyIF { int MyMeth(int x); }
то следующая его реализация считается вполне допустимой: class MyClass : IMyIF { int IMyIF.MyMeth(int x) { return x / 3; } }
Как видите, при реализации члена MyMeth() интерфейса IMyIF указывается его полное имя, включающее в себя имя его интерфейса.
Для явной реализации интерфейсного метода могут быть две причины. Во-первых, когда интерфейсный метод реализуется с указанием его полного имени, то такой ме тод оказывается доступным не посредством объектов класса, реализующего данный интерфейс, а по интерфейсной ссылке. Следовательно, явная реализация позволяет реализовать интерфейсный метод таким образом, чтобы он не стал открытым членом класса, предоставляющего его реализацию. И во-вторых, в одном классе могут быть реализованы два интерфейса с методами, объявленными с одинаковыми именами и сигнатурами. Но неоднозначность в данном случае устраняется благодаря указанию в именах этих методов их соответствующих интерфейсов. Рассмотрим каждую из этих двух возможностей явной реализации на конкретных примерах.
В приведенном ниже примере программы демонстрируется интерфейс IEven, в котором объявляются два метода: IsEven() и IsOdd(). В первом из них определяет ся четность числа, а во втором – его нечетность. Интерфейс IEven затем реализуется в классе MyClass. При этом метод IsOdd() реализуется явно. // Реализовать член интерфейса явно. using System; interface IEven { bool IsOdd(int x); bool IsEven(int x); } class MyClass : IEven { // Явная реализация. Обратите внимание на то, что // этот член является закрытым по умолчанию. bool IEven.IsOdd(int x) { if((x%2) != 0) return true; else return false; } // Обычная реализация, public bool IsEven(int x) { IEven о = this; // Интерфейсная ссылка на вызывающий объект. return !о.IsOdd(х); } } class Demo { static void Main() { MyClass ob = new MyClass(); bool result; result = ob.IsEven(4); if(result) Console.WriteLine("4 – четное число."); // result = ob.IsOdd(4); // Ошибка, член IsOdd интерфейса IEven недоступен // Но следующий код написан верно, поскольку в нем сначала создается // интерфейсная ссылка типа IEven на объект класса MyClass, а затем по // этой ссылке вызывается метод IsOdd(). IEven iRef = (IEven) ob; result = iRef.IsOdd(3); if(result) Console.WriteLine("3 – нечетное число."); } }
В приведенном выше примере метод IsOdd() реализуется явно, а значит, он недо ступен как открытый член класса MyClass. Напротив, он доступен только по интер фейсной ссылке. Именно поэтому он вызывается посредством переменной о ссылоч ного типа IEven в реализации метода IsEven().
Ниже приведен пример программы, в которой реализуются два интерфейса, при чем в обоих интерфейсах объявляется метод Meth(). Благодаря явной реализации ис ключается неоднозначность, характерная для подобной ситуации. // Воспользоваться явной реализацией для устранения неоднозначности. using System; interface IMyIF_A { int Meth(int x); } interface IMyIF_B { int Meth(int x); } // Оба интерфейса реализуются в классе MyClass. class MyClass : IMyIF_A, IMyIF_B { // Реализовать оба метода Meth() явно. int IMyIF_A.Meth(int x) { return x + x; } int IMyIF_B.Meth(int x) { return x * x; } // Вызывать метод Meth() по интерфейсной ссылке. public int MethA(int x) { IMyIF_A a_ob; a_ob = this; return a_ob.Meth(x); // вызов интерфейсного метода IMyIF_A } public int MethB(int x){ IMyIF_B b_ob; b_ob = this; return b_ob.Meth(x); // вызов интерфейсного метода IMyIF_B } } class FQIFNames { static void Main() { MyClass ob = new MyClass(); Console.Write("Вызов метода IMyIF_A.Meth(): "); Console.WriteLine(ob.MethA(3)); Console.Write("Вызов метода IMyIF_B.Meth(): "); Console.WriteLine(ob.MethB(3)); } }
Вот к какому результату приводит выполнение этой программы. Вызов метода IMyIF_A.Meth(): 6 Вызов метода IMyIF_B.Meth(): 9
Анализируя приведенный выше пример программы, обратим прежде всего вни мание на одинаковую сигнатуру метода Meth() в обоих интерфейсах, IMyIF_A и IMyIF_B. Когда оба этих интерфейса реализуются в классе MyClass, для каждого из них в отдельности это делается явно, т.е. с указанием полного имени метода Meth(). А поскольку явно реализованный метод может вызываться только по интерфейсной ссылке, то в классе MyClass создаются две такие ссылки: одна – для интерфейса IMyIF_A, а другая – для интерфейса IMyIF_B. Именно по этим ссылкам происходит обращение к объектам данного класса с целью вызвать методы соответствующих ин терфейсов, благодаря чему и устраняется неоднозначность. Выбор между интерфейсом и абстрактным классом
Одна из самых больших трудностей программирования на C# состоит в правиль ном выборе между интерфейсом и абстрактным классом в тех случаях, когда требу ется описать функциональные возможности, но не реализацию. В подобных случаях рекомендуется придерживаться следующего общего правила: если какое-то понятие можно описать с точки зрения функционального назначения, не уточняя конкретные детали реализации, то следует использовать интерфейс. А если требуются некоторые детали реализации, то данное понятие следует представить абстрактным классом. Стандартные интерфейсы для среды .NET Framework
Для среды .NET Framework определено немало стандартных интерфейсов, которы ми можно пользоваться в программах на С#. Так, в интерфейсе System.IComparable определен метод CompareTo(), применяемый для сравнения объектов, когда требу ется соблюдать отношение порядка. Стандартные интерфейсы являются также важ ной частью классов коллекций, предоставляющих различные средства, в том числе стеки и очереди, для хранения целых групп объектов. Так, в интерфейсе System. Collections.ICollection определяются функции для всей коллекции, а в интер фейсе System.Collections.IEnumerator – способ последовательного обращения к элементам коллекции. Эти и многие другие интерфейсы подробнее рассматривают ся в части II данной книги. Структуры
Как вам должно быть уже известно, классы относятся к ссылочным типам данных. Это означает, что объекты конкретного класса доступны по ссылке, в отличие от значе ний простых типов, доступных непосредственно. Но иногда прямой доступ к объектам как к значениям простых типов оказывается полезно иметь, например, ради повыше ния эффективности программы. Ведь каждый доступ к объектам (даже самым мелким) по ссылке связан с дополнительными издержками на расход вычислительных ресурсов и оперативной памяти. Для разрешения подобных затруднений в C# предусмотрена структура, которая подобна классу, но относится к типу значения, а не к ссылочному типу данных.
Структуры объявляются с помощью ключевого слова struct и с точки зрения син таксиса подобны классам. Ниже приведена общая форма объявления структуры: struct имя : интерфейсы { // объявления членов }
где имя обозначает конкретное имя структуры.
Одни структуры не могут наследовать другие структуры и классы или служить в качестве базовых для других структур и классов. (Разумеется, структуры, как и все остальные типы данных в С#, наследуют класс object.) Тем не менее в структуре мож но реализовать один или несколько интерфейсов, которые указываются после имени структуры списком через запятую. Как и у классов, у каждой структуры имеются свои члены: методы, поля, индексаторы, свойства, операторные методы и события. В струк турах допускается также определять конструкторы, но не деструкторы. В то же время для структуры нельзя определить конструктор, используемый по умолчанию (т.е. кон структор без параметров). Дело в том, что конструктор, вызываемый по умолчанию, определяется для всех структур автоматически и не подлежит изменению. Такой кон структор инициализирует поля структуры значениями, задаваемыми по умолчанию. А поскольку структуры не поддерживают наследование, то их члены нельзя указывать как abstract, virtual или protected.
Объект структуры может быть создан с помощью оператора new таким же обра зом, как и объект класса, но в этом нет особой необходимости. Ведь когда используется оператор new, то вызывается конструктор, используемый по умолчанию. А когда этот оператор не используется, объект по-прежнему создается, хотя и не инициализируется.
В этом случае инициализацию любых членов структуры придется выполнить вручную. В приведенном ниже примере программы демонстрируется применение структу ры для хранения информации о книге. // Продемонстрировать применение структуры. using System; // Определить структуру. struct Book { public string Author; public string Title; public int Copyright; public Book(string a, string t, int c) { Author = a; Title = t; Copyright = c; } } // Продемонстрировать применение структуры Book. class StructDemo { static void Main() { Book book1 = new Book("Герберт Шилдт", "Полный справочник пo C# 4.0", 2010) ; // вызов явно заданного конструктора Book book2 = new Book(); // вызов конструктора по умолчанию Book bоок3; // конструктор не вызывается Console.WriteLine(book1.Author + ", " + book1.Title + ", (c) " + book1.Copyright); Console.WriteLine(); if(book2.Title == null) Console.WriteLine("Член book2.Title пуст."); // А теперь ввести информацию в структуру book2. book2.Title = "О дивный новый мир"; book2.Author = "Олдос Хаксли"; book2.Copyright = 1932; Console.Write("Структура book2 теперь содержит:n"); Console.WriteLine(book2.Author + ", " + book2.Title + ", (c) " + book2.Copyright); Console.WriteLine(); // Console.WriteLine(bоокЗ.Title); // неверно, этот член структуры // нужно сначала инициализировать bоокЗ.Title = "Красный шторм"; Console.WriteLine(bоокЗ.Title); // теперь верно } }
При выполнении этой программы получается следующий результат. Герберт Шилдт, Полный справочник по C# 4.0, (с) 2010 Член book2.Title пуст. Структура bоок2 теперь содержит: Олдос Хаксли, О дивный новый мир, (с) 1932 Красный шторм
Как демонстрирует приведенный выше пример программы, структура может быть инициализирована с помощью оператора new для вызова конструктора или же путем простого объявления объекта. Так, если используется оператор new, то поля структуры инициализируются конструктором, вызываемым по умолчанию (в этом случае во всех полях устанавливается задаваемое по умолчанию значение), или же конструктором, определяемым пользователем. А если оператор new не используется, как это имеет место для структуры bоок3, то объект структуры не инициализируется, а его поля должны быть установлены вручную перед тем, как пользоваться данным объектом.
Когда одна структура присваивается другой, создается копия ее объекта. В этом за ключается одно из главных отличий структуры от класса. Как пояснялось ранее в этой книге, когда ссылка на один класс присваивается ссылке на другой класс, в итоге ссылка в левой части оператора присваивания указывает на тот же самый объект, что и ссылка в правой его части. А когда переменная одной структуры присваивается переменной другой структуры, создается копия объекта структуры из правой части оператора при сваивания. Рассмотрим в качестве примера следующую программу. // Скопировать структуру. using System; // Определить структуру. struct MyStruct { public int x; } // Продемонстрировать присваивание структуры. class StructAssignment { static void Main() { MyStruct a; MyStruct b; a.x = 10; b.x = 20; Console.WriteLine("a.x {0}, b.x {1}", a.x, b.x); a = b; b.x = 30; Console.WriteLine("a.x {0}, b.x {1}", a.x, b.x); } }
Вот к какому результату приводит выполнение этой программы. а.х 10, b.x 20 a.x 20, b.x 30
Как показывает приведенный выше результат, после присваивания а = b;
переменные структуры а и b по-прежнему остаются совершенно обособленными, т.е. переменная а не указывает на переменную b и никак не связана с ней, помимо того, что она содержит копию значения переменной b. Ситуация была бы совсем иной, если бы переменные а и b были ссылочного типа, указывая на объекты определенного клас са. В качестве примера ниже приведен вариант предыдущей программы, где демон стрируется присваивание переменных ссылки на объекты определенного класса. // Использовать ссылки на объекты определенного класса. using System; // Создать класс. class MyClass { public int x; } // Показать присваивание разных объектов данного класса. class ClassAssignment { static void Main() { MyClass a = new MyClass(); MyClass b = new MyClass(); a.x = 10; b.x = 20; Console.WriteLine("a.x {0}, b.x {1}", a.x, b.x); a = b; b.x = 30; Console.WriteLine("а.х {0}, b.x {1}", a.x, b.x); } }
Выполнение этой программы приводит к следующему результату. а.х 10, b.x 20 а.х 30, b.x 30
Как видите, после того как переменная b будет присвоена переменной а, обе пере менные станут указывать на один и тот же объект, т.е. на тот объект, на который перво начально указывала переменная b. О назначении структур
В связи с изложенным выше возникает резонный вопрос: зачем в C# включена структура, если она обладает более скромными возможностями, чем класс? Ответ на этот вопрос заключается в повышении эффективности и производительности про грамм. Структуры относятся к типам значений, и поэтому ими можно оперировать непосредственно, а не по ссылке. Следовательно, для работы со структурой вообще не требуется переменная ссылочного типа, а это означает в ряде случаев существенную экономию оперативной памяти. Более того, работа со структурой не приводит к ухуд шению производительности, столь характерному для обращения к объекту класса. Ведь доступ к структуре осуществляется непосредственно, а к объектам – по ссылке, поскольку классы относятся к данным ссылочного типа. Косвенный характер доступа к объектам подразумевает дополнительные издержки вычислительных ресурсов на каждый такой доступ, тогда как обращение к структурам не влечет за собой подобные издержки. И вообще, если нужно просто сохранить группу связанных вместе данных, не требующих наследования и обращения по ссылке, то с точки зрения производи тельности для них лучше выбрать структуру.
Ниже приведен еще один пример, демонстрирующий применение структуры на практике. В этом примере из области электронной коммерции имитируется запись транзакции. Каждая такая транзакция включает в себя заголовок пакета, содержащий номер и длину пакета. После заголовка следует номер счета и сумма транзакции. Заго ловок пакета представляет собой самостоятельную единицу информации, и поэтому он организуется в отдельную структуру, которая затем используется для создания за писи транзакции или же информационного пакета любого другого типа. // Структуры удобны для группирования небольших объемов данных. using System; // Определить структуру пакета. struct PacketHeader { public uint PackNum; // номер пакета public ushort PackLen; // длина пакета } // Использовать структуру PacketHeader для создания записи транзакции // в сфере электронной коммерции. class Transaction { static uint transacNum = 0; PacketHeader ph; // ввести структуру PacketHeader в класс Transaction string accountNum; double amount; public Transaction(string acc, double val) { // создать заголовок пакета ph.PackNum = transacNum++; ph.PackLen = 512; // произвольная длина accountNum = acc; amount = val; } // Сымитировать транзакцию. public void sendTransaction() { Console.WriteLine("Пакет #: " + ph.PackNum + ", Длина: " + ph.PackLen + ",n Счет #: " + accountNum + ", Сумма: {0:C}n", amount); } } // Продемонстрировать применение структуры в виде пакета транзакции. class PacketDemo { static void Main() { Transaction t = new Transaction("31243", -100.12); Transaction t2 = new Transaction("AB4655", 345.25); Transaction t3 = new Transaction("8475-09", 9800.00); t.sendTransaction(); t2.sendTransaction(); t3.sendTransaction(); } }
Вот к какому результату может привести выполнение этого кода. Пакет #: 0, Длина: 512, Счет #: 31243, Сумма: ($100.12) Пакет #: 1, Длина: 512, Счет #: АВ4655, Сумма: $345.25 Пакет #: 2, Длина: 512, Счет #: 8475-09, Сумма: $9,800.00
Структура PacketHeader оказывается вполне пригодной для формирования заго ловка пакета транзакции, поскольку в ней хранится очень небольшое количество дан ных, не используется наследование и даже не содержатся методы. Кроме того, работа со структурой PacketHeader не влечет за собой никаких дополнительных издержек, связанных со ссылками на объекты, что весьма характерно для класса. Следовательно, структуру PacketHeader можно использовать для записи любой транзакции, не сни жая эффективность данного процесса.
Любопытно, что в С++ также имеются структуры и используется ключевое слово struct. Но эти структуры отличаются от тех, что имеются в С#. Так, в C++ структура относится к типу класса, а значит, структура и класс в этом языке практически равно ценны и отличаются друг от друга лишь доступом по умолчанию к их членам, которые оказываются закрытыми для класса и открытыми для структуры. А в С# структура от носится к типу значения, тогда как класс – к ссылочному типу. Перечисления
Перечисление представляет собой множество именованных целочисленных констант. Перечислимый тип данных объявляется с помощью ключевого слова enum. Ниже при ведена общая форма объявления перечисления: enum имя {список_перечисления};
где имя – это имя типа перечисления, а список_перечисления – список идентифи каторов, разделяемый запятыми. В приведенном ниже примере объявляется перечисление Apple различных сортов яблок. enum Apple { Jonathan, GoldenDel, RedDel, Winesap, Cortland, McIntosh };
Следует особо подчеркнуть, что каждая символически обозначаемая константа в перечислении имеет целое значение. Тем не менее неявные преобразования пере числимого типа во встроенные целочисленные типы и обратно в C# не определены, а значит, в подобных случаях требуется явное приведение типов. Кроме того, приве дение типов требуется при преобразовании двух перечислимых типов. Но поскольку перечисления обозначают целые значения, то их можно, например, использовать для управления оператором выбора switch или же оператором цикла for.
Для каждой последующей символически обозначаемой константы в перечислении задается целое значение, которое на единицу больше, чем у предыдущей константы. По умолчанию значение первой символически обозначаемой константы в перечисле нии равно нулю. Следовательно, в приведенном выше примере перечисления Apple константа Jonathan равна нулю, константа GoldenDel – 1, константа RedDel – 2 и т.д.
Доступ к членам перечисления осуществляется по имени их типа, после которого следует оператор-точка. Например, при выполнении фрагмента кода Console.WriteLine(Apple.RedDel + " имеет значение " + (int)Apple.RedDel);
выводится следующий результат. RedDel имеет значение 2
Как показывает результат выполнения приведенного выше фрагмента кода, для вы вода перечислимого значения используется его имя. Но для получения этого значения требуется предварительно привести его к типу int.
Ниже приведен пример программы, демонстрирующий применение перечисле ния Apple. // Продемонстрировать применение перечисления. using System; class EnumDemo { enum Apple { Jonathan, GoldenDel, RedDel, Winesap, Cortland, McIntosh }; static void Main() { string[] color = { "красный", "желтый", "красный", "красный", "красный", "красновато-зеленый" }; Apple i; // объявить переменную перечислимого типа // Использовать переменную i для циклического // обращения к членам перечисления. for(i = Apple.Jonathan; i <= Apple.McIntosh; i++) Console.WriteLine(i + " имеет значение " + (int)i); Console.WriteLine(); // Использовать перечисление для индексирования массива. for(i = Apple.Jonathan; i <= Apple.McIntosh; i++) Console.WriteLine("Цвет сорта " + i + " – " + color[(int)i]); } }
Ниже приведен результат выполнения этой программы. Jonathan имеет значение 0 GoldenDel имеет значение 1 RedDel имеет значение 2 Winsap имеет значение 3 Cortland имеет значение 4 McIntosh имеет значение 5 Цвет сорта Jonathan – красный Цвет сорта GoldenDel – желтый Цвет сорта RedDel – красный Цвет сорта Winsap – красный Цвет сорта Cortland – красный Цвет сорта McIntosh – красновато-зеленый
Обратите внимание на то, как переменная типа Apple управляет циклами for. Значения символически обозначаемых констант в перечислении Apple начинаются с нуля, поэтому их можно использовать для индексирования массива, чтобы получить цвет каждого сорта яблок. Обратите также внимание на необходимость производить приведение типов, когда перечислимое значение используется для индексирования массива. Как упоминалось выше, в C# не предусмотрены неявные преобразования перечислимых типов в целочисленные и обратно, поэтому для этой цели требуется явное приведение типов.
И еще одно замечание: все перечисления неявно наследуют от класса System.Enum, который наследует от класса System.ValueType, а тот, в свою очередь, – от класса object. Инициализация перечисления
Значение одной или нескольких символически обозначаемых констант в перечис лении можно задать с помощью инициализатора. Для этого достаточно указать после символического обозначения отдельной константы знак равенства и целое значение. Каждой последующей константе присваивается значение, которое на единицу больше значения предыдущей инициализированной константы. Например, в приведенном ниже фрагменте кода константе RedDel присваивается значение 10. enum Apple { Jonathan, GoldenDel, RedDel = 10, Winesap, Cortland, McIntosh };
В итоге все константы в перечислении принимают приведенные ниже значения. Jonathan 0 GoldenDel 1 RedDel 10 Winesap 11 Cortland 12 McIntosh 13 Указание базового типа перечисления
По умолчанию в качестве базового для перечислений выбирается тип int, тем не менее перечисление может быть создано любого целочисленного типа, за исключени ем char. Для того чтобы указать другой тип, кроме int, достаточно поместить этот тип после имени перечисления, отделив его двоеточием. В качестве примера ниже за дается тип byte для перечисления Apple. enum Apple : byte { Jonathan, GoldenDel, RedDel, Winesap, Cortland, McIntosh };
Теперь константа Apple.Winesap, например, имеет количественное значение типа byte. Применение перечислений
На первый взгляд перечисления могут показаться любопытным, но не очень нуж ным элементом С#, но на самом деле это не так. Перечисления очень полезны, когда в программе требуется одна или несколько специальных символически обозначаемых констант. Допустим, что требуется написать программу для управления лентой кон вейера на фабрике. Для этой цели можно создать метод Conveyor(), принимающий в качестве параметров следующие команды: "старт", "стоп", "вперед" и "назад". Вместо того чтобы передавать методу Conveyor() целые значения, например, 1 – в качестве команды "старт", 2 – в качестве команды "стоп" и так далее, что чревато ошибками, можно создать перечисление, чтобы присвоить этим значениям содержательные сим волические обозначения. Ниже приведен пример применения такого подхода. // Сымитировать управление лентой конвейера. using System; class ConveyorControl { // Перечислить команды конвейера. public enum Action { Start, Stop, Forward, Reverse }; public void Conveyor(Action com) { switch(com) { case Action.Start: Console.WriteLine("Запустить конвейер."); break; case Action.Stop: Console.WriteLine("Остановить конвейер."); break; case Action.Forward: Console.WriteLine("Переместить конвейер вперед."); break; case Action.Reverse: Console.WriteLine("Переместить конвейер назад."); break; } } } class ConveyorDemo { static void Main() { ConveyorControl с = new ConveyorControl(); с.Conveyor(ConveyorControl.Action.Start); с.Conveyor(ConveyorControl.Action.Forward); с.Conveyor(ConveyorControl.Action.Reverse); с.Conveyor(ConveyorControl.Action.Stop); } }