Главная страница
Навигация по странице:

  • Наследование интерфейсов

  • Сокрытие имен с помощью наследования интерфейсов

  • Явная реализация членов интерфейса

  • Как избежать неопределенности с помощью явной реализации

  • Выбор между интерфейсом и абстрактным классом

  • Стандартные интерфейсы среды .NET Framework

  • Задание базового типа перечисления

  • Использование перечислений

  • Справочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией


    Скачать 5.05 Mb.
    НазваниеСправочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией
    АнкорC #.pdf
    Дата08.12.2017
    Размер5.05 Mb.
    Формат файлаpdf
    Имя файлаC #.pdf
    ТипСправочник
    #10795
    страница21 из 52
    1   ...   17   18   19   20   21   22   23   24   ...   52

    330
    Часть I. Язык C#
    Console.WriteLine("\nНaчинaeм с числа 21"); ob.next = 21; for(int i=0; i < 5; i++)
    Console.WriteLine("Следующее значение равно " + ob.next);
    Console.WriteLine("\nпереход в исходное состояние."); 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
    Следующее значение равно 2
    Следующее значение равно 4
    Следующее значение равно 6
    Следующее значение равно 8
    Наследование интерфейсов
    Один интерфейс может унаследовать “богатство” другого. Синтаксис этого механизма аналогичен синтаксису, используемому для наследования классов. Если класс реализует интерфейс, который наследует другой интерфейс, этот класс должен обеспечить способы реализации для всех членов, определенных внутри цепочки наследования интерфейсов. Рассмотрим такой пример:
    // Один интерфейс может наследовать другой. using System; public interface A { void meth1(); void meth2();
    }
    // Интерфейс В теперь включает методы meth1() и meth2(),
    //а также добавляет метод meth3().

    Глава 12. Интерфейсы, структуры и перечисления
    331 public interface В : A { void meth3();
    }
    // Этот класс должен реализовать все методы
    // интерфейсов А и В. class MyClass : В { public void meth1() {
    Console.WriteLine("Реализация метода meth1().");
    } public void meth2() {
    Console.WriteLine("Реализация метода meth2().");
    } public void meth3() {
    Console.WriteLine("Реализация метода meth3().");
    }
    } class IFExtend { public static void Main() {
    MyClass ob = new MyClass(); ob.meth1(); ob.meth2(); ob.meth3();
    }
    }
    Если бы в качестве эксперимента вы попытались удалить метод meth1()
    , реализованный в классе
    MyClass
    , то сразу же получили бы от компилятора сообщение об ошибке. Как упоминалось выше, любой класс, который реализует интерфейс, должен реализовать все методы, определенные этим интерфейсом, включая методы, которые унаследованы от других интерфейсов.
    Сокрытие имен с помощью наследования
    интерфейсов
    В производном интерфейсе можно объявить член, который скрывает член, определенный в базовом интерфейсе. Это происходит при совпадении их сигнатур. Такое совпадение вызовет предупреждающее сообщение, если член производного интерфейса не модифицировать с помощью ключевого слова new
    Явная реализация членов интерфейса
    При реализации члена интерфейса можно квалифицировать его имя с использованием имени интерфейса. В этом случае говорят, что член интерфейса
    реализуется явным образом, или имеет место его явная реализация. Например, при определении интерфейса

    332
    Часть I. Язык C# interface IMyIF { int myMeth(int x);
    } вполне допустимо реализовать интерфейс
    IMyIF
    следующим образом: class MyClass : IMyIF{ int IMyIF.myMeth(int x) { return x / 3;
    }
    }
    Как видите, при реализации метода myMeth()
    члена интерфейса
    IMyIF
    указывается его полное имя, включающее имя интерфейса.
    Явная реализация членов интерфейса может понадобиться по двум причинам. Во- первых, реализуя метод с использованием полностью квалифицированного имени, вы тем самым обозначаете части закрытой реализации, которые не “видны” коду, определенному вне класса. Во-вторых, класс может реализовать два интерфейса, которые объявляют методы с одинаковыми именами и типами. Полная квалификация имен позволяет избежать неопределенности ситуации. Рассмотрим примеры.
    Закрытая реализация
    Следующая программа содержит интерфейс с именем
    IEven
    , который определяет два метода isEven()
    и isOdd()
    , устанавливающие факт четности и нечетности числа, соответственно. Класс MyClass реализует интерфейс
    IEven
    , причем его член 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 o = this; // Ссылка на вызывающий объект. return !o.isOdd(x);
    }
    } class Demo { public static void Main() {
    MyClass ob = new MyClass(); bool result; result = ob.isEven(4); if(result) Console.WriteLine("4 - четное число.");

    Глава 12. Интерфейсы, структуры и перечисления
    333 else Console.WriteLine("3 - нечетное число.");
    // result = ob.isOdd(); // Ошибка, член не виден.
    }
    }
    Поскольку метод isOdd()
    реализован в явном виде, он недоступен вне класса
    MyClass
    . Такой способ реализации делает его надежно закрытым. Внутри класса
    MyClass к методу isOdd()
    можно получить доступ только через ссылку на интерфейс.
    Вот почему он прекрасно вызывается для объекта о в реализации метода 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
    }
    }

    334
    Часть I. Язык C# class FQIFNames { public 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()
    , объявленный в интерфейсе
    IMyIF_A
    , создает ссылку на интерфейс
    IMyIF_A
    , а метод meth()
    , объявленный в интерфейсе
    IMyIF_B
    , создает ссылку на интерфейс
    IMyIF_B
    . Созданные ссылки затем используются при вызове этих методов, благодаря чему можно избежать неоднозначности.
    Выбор между интерфейсом и абстрактным
    классом
    В программировании на C# при необходимости описать функции, а не способ их реализации, важно знать, когда следует использовать интерфейс, а когда — абстрактный класс. Общее правило таково. Если вы полностью описываете действия класса и не нужно уточнять, как он это делает, следует использовать интерфейс. Если же требуется включить в описание детали реализации, имеет смысл представить концепцию программы (или ее части) в виде абстрактного класса.
    Стандартные интерфейсы среды .NET Framework
    В среде .NET Framework определено множество интерфейсов, которые могут использовать C#-программы. Например, интерфейс
    System.IComparable определяет метод
    CompareTo()
    , который позволяет сравнивать объекты. Интерфейсы также образуют важную часть коллекции классов, которая обеспечивает различные типы для хранения групп объектов (например, стеки и очереди). Так, например, интерфейс
    System.Collections.ICollection определяет функциональность, общую для всех коллекций. Интерфейс
    System.Collections.IEnumerator предлагает способ опроса элементов в коллекции. Эти и другие интерфейсы мы рассмотрим в части II.

    Глава 12. Интерфейсы, структуры и перечисления
    335
    Учебный проект: создание интерфейса
    Прежде чем продолжать изучение других средств программирования на C#, было бы полезно рассмотреть пример использования интерфейса. В этом разделе мы создадим интерфейс
    ICipher
    , который определяет методы поддержки шифрования строк. Для этой задачи использование интерфейса вполне оправданно, поскольку здесь можно полностью отделить код с описанием “что” от кода, где указано, “как”.
    Интерфейс
    ICipher имеет такой вид:
    // Интерфейс шифрования и дешифрирования строк. public interface ICipher { string encode(string str); string decode(string str);
    }
    В интерфейсе
    ICipher объявляются два метода: encode()
    и decode()
    , которые используются для шифрования и дешифрирования строк, соответственно. При этом другие детали не уточняются. Это значит, что классы, которые будут реализовывать эти методы, могут выбирать любой метод шифрования. Например, один класс мог бы шифровать строку на основе ключа, определенного пользователем. Другой мог бы использовать защиту с помощью системы паролей. У третьего механизм действия шифра мог бы опираться на побитовую обработку, а у четвертого — на простую перестановку кода (реализация перестановочного шифра). Главное то, что интерфейс операции шифрования и дешифрирования строк не зависит от используемого способа шифрования. А поскольку здесь нет необходимости определять даже часть механизма шифрования, то для его представления мы выбираем средство интерфейса.
    В нашем учебном проекте интерфейс
    ICipher реализуют сразу два класса:
    SimpleCipher и
    BitCipher
    . Класс
    SimpleCipher шифрует строку посредством сдвига каждого символа на одну “алфавитную” позицию выше. Например, в результате такого сдвига буква А становится буквой Б, а буква Б — буквой В и т.д. Класс
    BitCipher шифрует строку по-другому: каждый символ заменяется результатом операции исключающего ИЛИ, примененной к этому символу и некоторому 16-разрядному значению, которое используется в качестве ключа.
    /* Простая реализация интерфейса ICipher, которая кодирует сообщение посредством сдвига каждого символа на
    1 позицию вверх. Так, буква А превратится в букву Б и т.д. */ class SimpleCipher : ICipher {
    // Метод возвращает зашифрованную строку, заданную
    // открытым текстом. public string encode(string str) { string ciphertext = ""; for(int i=0; i < str.Length; i++) ciphertext = ciphertext + (char) (str[i] + 1); return ciphertext;
    }
    //
    Метод возвращает дешифрированную строку, заданную
    // зашифрованным текстом. public string decode(string str) { string plaintext = "";

    336
    Часть I. Язык C# for(int i=0; i < str.Length; i++) plaintext = plaintext + (char) (str[i] - 1); return plaintext;
    }
    }
    /* В этой реализации интерфейса ICipher используется побитовая обработка и ключ. */ class BitCipher : ICipher { ushort key;
    //
    Определяем ключ при построении объектов
    // класса BitCipher. public BitCipher(ushort k) { key = k;
    }
    //
    Метод возвращает зашифрованную строку, заданную
    // открытым текстом. public string encode(string str) { string ciphertext = ""; for(int i=0; i < str.Length; i++) ciphertext = ciphertext + (char) (str[i] ^ key); return ciphertext;
    }
    //
    Метод возвращает дешифрированную строку, заданную
    // зашифрованным текстом. public string decode(string str) { string plaintext = ""; for(int i=0; i < str.Length; i++) plaintext = plaintext + (char) (str[i] ^ key); return plaintext;
    }
    }
    Как видите, оба класса
    SimpleCipher и
    BitCipher реализуют один и тот же интерфейс
    ICipher
    , хотя используют при этом различные способы его реализации. В следующей программе демонстрируется функционирование классов
    SimpleCipher и
    BitCipher
    // Демонстрация использования интерфейса ICipher. using System; class ICipherDemo { public static void Main() {
    ICipher ciphRef;
    BitCipher bit = new BitCipher(27);
    SimpleCipher sc = new SimpleCipher(); string plain; string coded;

    Глава 12. Интерфейсы, структуры и перечисления
    337
    //
    Сначала переменная ciphRef ссылается на объект
    // класса SimpleCipher (простое шифрование). ciphRef = sc;
    Console.WriteLine("Использование простого шифра."); plain = "testing"; coded = ciphRef.encode(plain);
    Console.WriteLine("Зашифрованный текст: " + coded); plain = ciphRef.decode(coded);
    Console.WriteLine("Открытый текст: " + plain);
    //
    Теперь переменная ciphRef refer ссылается на
    // объект класса BitCipher (поразрядное шифрование). ciphRef = bit;
    Console.WriteLine(
    "\nИспользование поразрядного шифрования."); plain = "testing"; coded = ciphRef.encode(plain);
    Console.WriteLine("Зашифрованный текст: " + coded); plain = ciphRef.decode(coded);
    Console.WriteLine("Открытый текст: " + plain);
    }
    }
    Вот результаты выполнения этой программы:
    Использование простого шифра.
    Зашифрованный текст: uftujoh
    Открытый текст: testing
    Использование поразрядного шифрования.
    Зашифрованный текст: o

    horu|
    Открытый текст: testing
    Одно из достоинств создания интерфейса шифрования состоит в том, что доступ к любому классу, который реализует этот интерфейс, осуществляется одинаково, независимо от того, как реализован процесс шифрования. Например, рассмотрим следующую программу, в которой класс
    UnlistedPhone используется для хранения телефонных номеров в зашифрованном формате. При необходимости имена и цифры номера автоматически дешифруются.
    // Использование интерфейса ICipher. using System;
    // Класс для хранения телефонных номеров. class UnlistedPhone { string pri_name; // Поддерживает свойство Name. string pri_number; // Поддерживает свойство Number.
    ICipher crypt; // Ссылка на объект шифрования. public UnlistedPhone(string name, string number,
    ICipher c)
    {

    338
    Часть I. Язык C# crypt = c; // Хранит объект шифрования. pri_name = crypt.encode(name); pri_number = crypt.encode(number);
    } public string Name { get
    { return crypt.decode(pri_name);
    } set
    { pri_name
    = crypt.encode(value);
    }
    } public string Number { get
    { return crypt.decode(pri_number);
    } set
    { pri_number = crypt.encode(value);
    }
    }
    }
    // Демонстрируем использование класса UnlistedPhone. class UnlistedDemo { public static void Main() {
    UnlistedPhone phone1 = new UnlistedPhone("Tom", "555-3456", new BitCipher(27));
    UnlistedPhone phone2 = new
    UnlistedPhone("Мэри", "555-8891", new BitCipher(9));
    Console.WriteLine("Телефонный номер абонента по имени " + phone1.Name + " : " + phone1.Number);
    Console.WriteLine("Телефонный номер абонента по имени " + phone2.Name + " : " + phone2.Number);
    }
    }
    Вот результаты выполнения этой программы:
    Телефонный номер абонента по имени Том : 555-3456
    Телефонный номер абонента по имени Мэри : 555-8891
    Рассмотрим, как реализован класс
    UnlistedPhone
    . Обратите внимание на то, что он содержит три поля. Первые два представляют собой закрытые переменные для хранения имени и соответствующего ему телефонного номера. Третье поле — это ссылка на объект интерфейса
    ICipher
    . Объекту класса
    UnlistedPhone при создании передаются три ссылки. Первые две ссылаются на строки, содержащие имя и телефонный номер, а третья
    — на объект шифрования, который используется для кодирования имени и номера. Ссылка на объект шифрования хранится в переменной crypt
    . Здесь допустим объект шифрования любого типа, если, конечно, он реализует интерфейс

    Глава 12. Интерфейсы, структуры и перечисления
    339
    ICipher
    . В данном случае используется объект типа
    BitCipher
    . Таким образом, объект класса
    UnlistedPhone может вызывать методы encode()
    и decode()
    для объекта
    BitCipher через ссылку crypt
    Теперь обратите внимание на организацию работы свойств
    Name и
    Number
    . При выполнении set
    -операции имя или телефонный номер автоматически шифруются посредством вызова метода encode()
    для объекта, определяемого ссылкой crypt
    . При выполнении get
    -операции имя или телефонный номер автоматически дешифруются посредством вызова метода decode()
    . Ни свойству
    Name
    , ни свойству
    Number не известен используемый для них метод шифрования. Они просто получают доступ к его телу через интерфейс.
    Поскольку интерфейс шифрования стандартизирован описанием интерфейса
    ICipher
    , можно изменить объект шифрования, не изменяя внутренний код класса
    UnlistedPhone
    . Например, в следующей программе при создании объектов класса
    UnlistedPhone используется
    SimpleCipher
    -объект, а не
    BitCipher
    -объект. Сюда внесено единственное изменение, связанное с передачей объекта шифрования конструктору класса
    UnlistedPhone
    // Эта версия программы использует класс SimpleCipher. using System; class UnlistedDemo { public static void Main() {
    //На этот раз вместо класса Bitcipher используем
    // класс SimpleCipher.
    UnlistedPhone phone1= new
    UnlistedPhone("Tom",
    "555-3456", new
    SimpleCipher());
    UnlistedPhone phone2 = new
    UnlistedPhone("Mary",
    "555-88 91", new
    SimpleCipher());
    Console.WriteLine(
    "Телефонный номер абонента по имени " + phone1.Name + " : " + phone1.Number);
    Console.WriteLine(
    "Телефонный номер абонента по имени " + phone2.Name + " : " + phone2.Number);
    }
    }
    Как показывает эта программа, поскольку интерфейс
    ICipher реализуют оба класса

    SimpleCipher и
    Bitcipher
    , для создания объектов класса
    UnlistedPhone можно использовать любой из них.
    И последнее. Код класса
    UnlistedPhone также демонстрирует возможность доступа к объектам, которые реализуют интерфейс, посредством ссылки на него. Поскольку на объект шифрования можно указывать с помощью ссылочной переменной типа
    ICipher
    , для реализации процесса шифрования можно использовать любой объект, который реализует интерфейс
    ICipher
    . Это позволяет совершенно безболезненно и безопасно заменить один метод шифрования другим, не изменяя код класса
    UnlistedPhone
    . Но если бы в классе
    UnlistedPhone для данных типа crypt был

    340
    Часть I. Язык C# жестко определен тип объекта шифрования (например, класс
    BitCipher
    ), то при необходимости заменить схему шифрования в код класса
    UnlistedPhone пришлось бы вносить изменения.
    Структуры
    Как вы уже знаете, классы — это ссылочные типы. Это означает, что к объектам классов доступ осуществляется через ссылку. Этим они отличаются от типов значений, к которым в C# реализован прямой доступ. Но иногда желательно получать прямой доступ и к объектам, как в случае нессылочных типов. Одна из причин для этого — эффективность.
    Ведь очевидно, что доступ к объектам классов через ссылки увеличивает расходы системных ресурсов, в том числе и памяти. Даже для очень маленьких объектов требуются существенные объемы памяти. Для компенсации упомянутых расходов времени и пространства в C# предусмотрены структуры. Структура подобна классу, но она относится к типу значений, а не к ссылочным типам.
    Структуры объявляются с использованием ключевого слова struct и синтаксически подобны классам. Формат записи структуры таков: struct
    имя
    :
    интерфейсы
    {
    // объявления членов
    }
    Элемент
    имя
    означает имя структуры.
    Структуры не могут наследовать другие структуры или классы. Структуры не могут использоваться в качестве базовых для других структур или классов. (Однако, подобно другим C#-типам, структуры наследуют класс 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;
    }

    Глава 12. Интерфейсы, структуры и перечисления
    341
    }
    // Демонстрируем использование структуры Book. class StructDemo { public static void Main() {
    Book book1 = new Book( "Herb Schildt",
    "C#
    A
    Beginner's
    Guide",
    2001);
    //
    Вызов явно заданного
    // конструктора.
    Book book2 = new Book(); // Вызов конструктора
    // по умолчанию.
    Book book3; // Создание объекта без вызова
    // конструктора.
    Console.WriteLine(book1.title + ", автор " + book1.author
    +
    ", (c) " + book1.copyright);
    Console.WriteLine(); if(book2.title == null)
    Console.WriteLine("Член book2.title содержит null.");
    //
    Теперь поместим в структуру book2 данные. book2.title = "Brave New World"; book2.author = "Aldous Huxley"; book2.copyright = 1932;
    Console.Write("Теперь структура book2 содержит:\n ");
    Console.WriteLine(book2.title + ", автор " + book2.author
    +
    ",
    (c)
    "
    + book2.copyright);
    Console.WriteLine();
    //
    Console.WriteLine(bооk3.title); // Ошибка: сначала
    // необходима
    // инициализация. book3.title = "Red Storm Rising";
    Console.WriteLine(book3.title);
    //
    Теперь все Ok!
    }
    }
    Вот результаты выполнения этой программы:
    C# A Beginner's Guide, автор Herb Schildt, (c) 2001
    Член book2.title содержит null.
    Теперь структура book2 содержит:
    Brave New World, автор Aldous Huxley, (c) 1932
    Red Storm Rising
    Как видно из результатов выполнения этой программы, структура может быть создана либо с помощью оператора new
    , который вызывает соответствующий конструктор, либо простым объявлением объекта. При использовании оператора new поля структуры будут инициализированы, причем это сделает либо конструктор по умолчанию (он инициализирует все поля значениями по умолчанию), либо конструктор, определенный пользователем. Если оператор new не используется, как в случае объекта

    342
    Часть I. Язык C# bооk3
    , созданный таким образом объект остается неинициализированным, и его поля должны быть установлены до начала использования.
    При присваивании одной структуры другой создается копия этого объекта. Это — очень важное отличие struct
    -объекта от сlass
    -объекта. Как упоминалось выше, присваивая одной ссылке на класс другую, вы просто меняете объект, на который ссылается переменная, стоящая с левой стороны от оператора присваивания. А присваивая одной struct
    -переменной другую, вы создаете копию объекта, указанного с правой стороны от оператора присваивания. Рассмотрим, например, следующую программу:
    // Копирование структуры. using System;
    // Определяем структуру. struct MyStruct { public int x;
    }
    // Демонстрируем присваивание структуры. class StructAssignment { public 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);
    }
    }
    Эта программа генерирует следующие результаты. a.x 10, b.x 20 a.x 20, b.x 30
    Как подтверждают результаты выполнения этой программы, после присваивания а = b; структурные переменные а
    и b
    по-прежнему не зависят одна от другой. Другими словами, переменная а
    никак не связана с переменной b
    , если не считать, что переменная а
    содержит копию значения переменной b
    . Будь а
    и b
    ссылками на классы, все обстояло бы по- другому. Рассмотрим теперь class
    -версию предыдущей программы.
    // Копирование класса. using System;
    // Определяем класс. class MyClass { public int x;
    }
    // Теперь покажем присваивание объектов класса.

    Глава 12. Интерфейсы, структуры и перечисления
    343 class ClassAssignment { public 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("a.x {0}, b.x {1}", a.x, b.x);
    }
    }
    Вот какие результаты получены при выполнении этой программы: a.x 10, b.x 20 a.x 30, b.x 30
    Как видите, после присваивания объекта b
    переменной а
    обе переменные ссылаются на один и тот же объект, т.е. на тот, на который изначально ссылалась переменная b
    Зачем нужны структуры
    Вы могли бы выразить удивление, почему C# включает тип struct
    , если, казалось бы, он представляет собой “слаборазвитую” версию типа class
    . Ответ следует искать в эффективности и производительности. Поскольку структуры — это типы значений, они обрабатываются напрямую, а не через ссылки. Таким образом, тип struct не требует отдельной ссылочной переменной. Это означает, что при использовании структур расходуется меньший объем памяти. Более того, благодаря прямому доступу к структурам, при работе с ними не снижается производительность, что имеет место при доступе к объектам классов. Поскольку классы — ссылочные типы, доступ к их объектам осуществляется через ссылки. Такая косвенность увеличивает затраты системных ресурсов при каждом доступе. Структуры этим не страдают. В общем случае, если вам нужно хранить небольшую группу связанных данных, но не нужно обеспечивать наследование и использовать другие достоинства ссылочных типов, тип struct может оказаться более предпочтительным вариантом.
    Рассмотрим еще один пример, демонстрирующий, как использовать структуру на практике. Эта программа имитирует запись транзакции. Одна запись содержит заголовок пакета с его номером и длиной. За этими данными указывается номер счета и объем транзакции. Поскольку заголовок пакета представляет собой самодостаточную единицу информации, он организован в виде структуры. Эту структуру затем можно использовать для создания записи транзакции или любого другого типа информационного пакета.
    // Структуры прекрасно работают при группировании данных. using System;
    // Определяем структуру пакета. struct PacketHeader { public uint packNum; // Номер пакета. public ushort packLen; // Длина пакета.
    }

    344
    Часть I. Язык C#
    // Используем структуру PacketHeader для создания
    // электронной записи транзакции. class Transaction { static uint transacNum = 0;
    PacketHeader ph;
    // Включаем в транзакцию
    // структуру PacketHeader. string accountNum; double amount; public Transaction(string acc, double val) {
    //
    Создаем заголовок пакета. ph.packNum = transacNum++; ph.packLen = 512; // arbitrary length accountNum = acc; amount = val;
    }
    //
    Имитируем транзакцию. public void sendTransaction() {
    Console.WriteLine("Пакет #: " + ph.packNum +
    ", Длина: " + ph.packLen +
    ",\n
    Счет #: " + accountNum +
    ", Сумма: {0:C}\n", amount);
    }
    }
    // Демонстрируем использование пакетной обработки. class PacketDemo { public 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 можно использовать для записей транзакций любого типа без ущерба для эффективности.

    Глава 12. Интерфейсы, структуры и перечисления
    345
    Интересно отметить, что в языке C++ также можно определять структуры с помощью ключевого слова struct
    . Однако C#- и C++-структуры имеют кардинальное отличие. В
    C++ тип struct
    — эти тип класса, причем типы struct и class практически эквивалентны (различие между ними выражается в доступе к их членам по умолчанию: для класса он принят закрытым, а для структуры — открытым). В C# ключевое слово struct используется для определения типов значений, a class
    — ссылочных типов.
    Перечисления
    Перечисление (enumeration) — это множество именованных целочисленных констант.
    Ключевое слово enum объявляет перечислимый тип. Формат записи перечисления таков: enum
    имя
    {
    список_перечисления
    };
    Здесь с помощью элемента
    имя
    указывается имя типа перечисления. Элемент
    список_перечисления
    представляет собой список идентификаторов, разделенных запятыми.
    Рассмотрим пример. В следующем фрагменте кода определяется перечисление apple
    , которое содержит список названий различных сортов яблок. enum apple { Jonathan, GoldenDel, RedDel, Winsap,
    Cortland,
    McIntosh
    };
    Здесь важно понимать, что каждый символ списка перечисления означает целое число, причем каждое следующее число (представленное идентификатором) на единицу больше предыдущего. Поскольку значение первого символа перечисления равно нулю, следовательно, идентификатор
    Jonathan имеет значение 0,
    GoldenDel
    — значение 1 и т.д.
    Константу перечисления можно использовать везде, где допустимо целочисленное значение. Однако между типом enum и встроенным целочисленным типом неявные преобразования не определены, поэтому при необходимости должна использоваться явно заданная операция приведения типов. В случае преобразования одного типа перечисления в другой также необходимо использовать приведение типов.
    К членам перечисления доступ осуществляется посредством имени типа и оператора
    “точка”. Например, при выполнении инструкции
    Console.WriteLine(apple.RedDel + " имеет значение " +
    (int)apple.RedDel); будет отображено следующее.
    RedDel имеет значение 2
    Как подтверждает результат выполнения этой инструкции, при отображении значения перечислимого типа используется его имя. А для получения его целочисленного значения необходимо использовать операцию приведения к типу int
    . (В этом заключается отличие от ранних версий C#, в которых по умолчанию отображалось целочисленное представление значения перечислимого типа, а не его имя.)
    Теперь рассмотрим программу, которая демонстрирует использование перечисления apple
    // Демонстрация использования перечисления. using System; class EnumDemo {

    346
    Часть I. Язык C# enum apple { Jonathan, GoldenDel, RedDel, Winsap,
    Cortland,
    McIntosh
    }; public 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 - красно-зеленый
    Обратите внимание на то, как for
    -циклы управляются переменной типа apple
    Поскольку перечисление — это целочисленный тип, значение перечисления может быть использовано везде, где допустимы целые значения. Поскольку значения перечислимого типа начинаются с нуля, их можно использовать для индексирования массива color
    (чтобы получить цвет яблок). Заметьте: в этом случае необходимо выполнить приведение типа. Как упоминалось выше, неявные преобразования между целочисленными и перечислимыми типами не определены. Поэтому без явно заданного приведения типа здесь не обойтись.
    И еще. Поскольку перечислимые типы представляют собой целочисленные значения, их можно использовать для управления switch
    -инструкцией (соответствующий пример приведен ниже).

    Глава 12. Интерфейсы, структуры и перечисления
    347
    Инициализация перечислений
    Одно или несколько символов в перечислении можно определить с помощью инициализатора. Это реализуется путем использования знака “равно” и последующего целого значения. Символам, стоящим после инициализатора, присваиваются значения, превышающие предыдущее значение инициализации. Например, следующий фрагмент кода присваивает число 10 символу
    RedDel enum apple { Jonathan, GoldenDel, RedDel = 10, Winsap,
    Cortland,
    McIntosh
    };
    Вот какие значения имеют теперь эти символы:
    Jonathan 0
    GoldenDel l
    RedDel 10
    Winsap 11
    Cortland 12
    McIntosh 13
    Задание базового типа перечисления
    По умолчанию перечисления используют тип int
    , но можно также создать перечисление любого другого целочисленного типа, за исключением типа char
    . Чтобы задать тип, отличный от int
    , укажите этот базовый тип после имени перечисления и двоеточия. Например, следующая инструкция создает перечисление apple с базовым типом byte enura apple : byte { Jonathan, GoldenDel, RedDel, Winsap,
    Cortland,
    McIntosh
    };
    Теперь член apple.Winsap
    , например, представляет собой byte
    -значение.
    Использование перечислений
    На первый взгляд может показаться, что перечисления, хотя и представляют определенный интерес, но вряд ли заслуживают большого внимания C#-программиста. Это не так. Перечисления незаменимы, когда в программе необходимо использовать один или несколько специализированных символов. Например, представьте, что вы пишете программу для управления лентой конвейера на фабрике. Вы могли бы создать метод conveyor()
    , который в качестве параметров принимает следующие параметры: старт, стоп, вперед и назад. Вместо того чтобы передавать методу conveyor()
    числа (1 для команды “старт”, 2 для команды “стоп” и т.д.), что чревато ошибками, вы можете создать перечисление, которое этим числам присваивает слова. Вот пример такого решения:
    // Управление лентой конвейера. using System; class ConveyorControl {
    //
    Перечисляем команды, управляющие конвейером. public enum action { старт, стоп, вперед, назад }; public void conveyor(action com) { switch(com)
    { case action.старт:
    Console.WriteLine("Запуск конвейера.");

    348
    Часть I. Язык C# break; case action.стоп:
    Console.WriteLine("Останов конвейера."); break; case action.вперед:
    Console.WriteLine("Перемещение вперед."); break; case action.назад:
    Console.WriteLine("Перемещение назад."); break;
    }
    }
    } class ConveyorDemo { public static void Main() {
    ConveyorControl c = new ConveyorControl(); c.conveyor(ConveyorControl.action.старт); c.conveyor(ConveyorControl.action.вперед); c.conveyor(ConveyorControl.action.назад); c.conveyor(ConveyorControl.action.стоп);
    }
    }
    Вот какие результаты генерирует эта программа:
    Запуск конвейера.
    Перемещение вперед.
    Перемещение назад.
    Останов конвейера.
    Поскольку метод conveyor()
    принимает аргумент типа action
    , этому методу могут передаваться только значения, имеющие тип action
    . Вот, например, попытка передать методу conveyor()
    значение 22. c.conveyor(22); // Ошибка!
    Эта инструкция не скомпилируется, поскольку встроенного преобразования из типа int в тип action не существует. Это — своего рода защита от передачи методу conveyor()
    некорректных команд. Конечно, чтобы “настоять” на таком преобразовании, можно было бы использовать операцию приведения типов, но это потребовало бы заранее продуманных действий и вряд ли привело к случайной ошибке. Кроме того, поскольку команды задаются словами, а не числами, маловероятно, что пользователь метода conveyor()
    по небрежности передаст неверное значение.
    В этом примере хотелось бы еще обратить ваше внимание вот на что. Тип перечисления используется здесь для управления switch
    -инструкцией. Как уже упоминалось, поскольку перечисления считаются целочисленными типами, их можно использовать в инструкции switch

    Полный справочник по
    1   ...   17   18   19   20   21   22   23   24   ...   52


    написать администратору сайта