Тверской государственный университет
Опубликован: 02.12.2009 | Доступ: свободный | Студентов: 2368 / 261 | Оценка: 4.47 / 4.24 | Длительность: 14:45:00
Лекция 4:

Перечисления

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >

Шкалы

Что можно делать с объектами перечислений? В приведенных выше примерах демонстрировалось, как эти объекты создаются, как получают значения, как используются они в анализе ситуаций - в операторах выбора if и switch. Возможно, наиболее интересное их применение - когда они задают шкалы. О шкалах уже шла речь, когда рассматривались логические операции над целыми числами. Обсудим работу со шкалами более подробно.

Вначале опишем ситуацию, когда полезно вводить в рассмотрение шкалу. Предположим, что проектируется некоторый содержательный класс, задающий описание множества объектов, например, класс Car, описывающий автомобили, или класс Employee, представляющий описание служащих некоторой фирмы. Для объектов этого класса зададим набор из n бинарных свойств. Бинарность свойства означает, что свойство может принимать только два значения. Служащий может владеть или не владеть иностранными языками, автомобиль может быть легковым или не быть таковым. В таких ситуациях набор таких свойств удобно представить перечислением, заданным в виде шкалы, а в соответствующий содержательный класс следует включить поле, тип которого задан этим перечислением.

Теперь формальное определение.

Перечисление, содержащее n элементов, будем называть шкалой, если отображение задано для каждого элемента перечисления и элемент с индексом k отображается в число 2k.

Каждый объект перечисления, заданного шкалой, представляется целым числом в диапазоне [0, 2n -1]. Это число следует рассматривать как число в двоичной системе счисления - набор из n битов (разрядов), каждый из которых описывает соответствующее свойство объекта. Единица в разряде указывает, что объект обладает данным свойством; ноль означает отсутствие свойства. Для объектов рассматриваемого нами класса Person можно ввести в рассмотрение набор из трех свойств - доброта, ум, богатство. Определим соответствующее перечисление как шкалу

public enum Dream_Properties
  {
    умный = 1, добрый = 2,
    богатый = 4
  }

Добавим в класс Person соответствующее поле и метод-свойство для доступа к нему:

Dream_Properties properties;
public Dream_Properties Properties
{
  get { return properties; }
  set { properties = value; }
}
Шкалы и запросы

Чем хороши шкалы? Тем, что они позволяют просто и эффективно реализовать запросы, позволяющие отобрать среди множества объектов те, которые обладают нужным набором свойств. Достигается это за счет того, что над объектами перечисления, заданного шкалой, определены логические операции, выполняемые над соответствующими парами битов, так что одна операция выполняется над всеми свойствами, входящими в шкалу.

Пусть для определенности у нас есть некоторый объект pattern, принадлежащий перечислению Dream_Properties и представляющий образец поиска, и объект person класса Person, одно из полей которого является объектом данного перечисления. Рассмотрим различные запросы на соответствие объекта person заданному образцу и реализацию запросов с использованием логических операций.

Запрос на поиск элемента, соответствующего образцу

Как проверить, что объект person обладает всеми свойствами, указанными в образце?

Вот реализация такого запроса:

(person.Properties & pattern) == pattern

Значение true этого выражения говорит о том, что объект удовлетворяет запросу и обладает всеми нужными свойствами.

Запрос на поиск элемента, у которого нет свойств, заданных в образце

Как проверить, что объект person не обладает ни одним из свойств, указанных в образце?

Вот реализация такого запроса:

(~person.Properties  & pattern) == pattern

Значение true этого выражения говорит о том, что объект удовлетворяет запросу.

Запрос на поиск элемента, обладающего некоторыми свойствами образца

Как проверить, что объект person обладает некоторыми свойствами, указанными в образце?

Вот реализация такого запроса:

(person.Properties & pattern) > 0

Значение true этого выражения говорит о том, что объект удовлетворяет запросу.

Запрос на поиск элемента, обладающего некоторыми, но не всеми свойствами образца

Как проверить, что объект person обладает некоторыми свойствами, но не всеми свойствами, указанными в образце?

Вот реализация такого запроса:

((person.Properties & pattern) > 0 ) && 
((person.Properties & pattern) < pattern)

Значение true этого выражения говорит о том, что объект удовлетворяет запросу.

Этот запрос ярко иллюстрирует операции отношения при работе с объектами перечислений. В первом операнде условной конъюнкции используется сравнение объекта перечисления с числом, во втором - сравнение двух объектов перечисления. Сравнивать объекты перечисления с числом можно, но если необходимо выполнить логическую поразрядную операцию над числом и объектом перечисления, то предварительно оба операнда необходимо привести к одному типу, неважно к какому. Логические поразрядные операции выполняются как над числами, так и над объектами перечислений.

Запрос на поиск элемента, в точности совпадающего с образцом

Как проверить, что объект person обладает всеми свойствами, указанными в образце, и никакими другими свойствами не обладает?

Вот реализация такого запроса:

((person.Properties & pattern) == pattern) &&
((person.Properties & ~pattern) == 0)

Значение true этого выражения говорит о том, что объект удовлетворяет запросу.

Шкалы и печать

Для объектов перечислений, как и положено, переопределен метод ToString. Его реализация, однако, не всегда удовлетворительна. Если числовое значение объекта таково, что в перечислении существует элемент, отображаемый в это число, то метод ToString возвращает в качестве строки элемент, заданный в перечислении. Если же, как это чаще всего бывает при работе со шкалами, числовое значение объекта перечисления получено в результате комбинации нескольких элементов, то в качестве результата возвращается числовое значение, а не комбинация элементов, как это хотелось бы. Этот недостаток метода ToString легко исправим. Для перечислений, задающих шкалу, разработана специальная реализация этого метода, выполняющая разбор численного значения и печатающая в качестве результата метода все идентификаторы, задающие свойства объекта перечисления. Для включения этой реализации метода ToString класс перечисления должен быть задан с атрибутом класса [Flags] (флажки). Этот атрибут сообщает компилятору, что перечисление является шкалой и для него необходимо использовать специальную версию метода ToString. Подробнее об этом поговорим в лекции, посвященной атрибутам.

Для понимания сути дела есть смысл написать собственную реализацию метода ToString, возвращающего комбинацию свойств объекта перечисления.

Рассмотрим реализацию этого метода для рассматриваемого нами перечисления Dream_Properties:

/// <summary>
 /// Аналог метода ToString для перечислений
 /// </summary>
 /// <param name="item">объект перечисления</param>
 /// <returns>строка со свойствами объекта</returns>
 string GetStrFromDreamObject(Dream_Properties item)
 {
    const int PROPERTIES_COUNT = 3;
    string str = "";
    int k = 1;
    for (int i = 1; i <= PROPERTIES_COUNT; i++)
    {
       if((k & (int)item) == k) 
       str += (Dream_Properties)k + " ";
       k <<= 1;
    }            
    return str;
 }

Анализируя код этого метода, стоит обратить внимание на несколько моментов.

  • В методе используются явные преобразования между типом int и перечислением. В первой строке цикла, прежде чем выполнять операцию поразрядного логического умножения, объект перечисления приводится к типу int. Во второй строке цикла выполняется обратное преобразование - числовое значение приводится к типу перечисления. Заметьте, что здесь же используется неявно вызываемый метод ToString для объекта перечисления.
  • Для шкал наряду с логическими операциями и операциями отношения часто используются и операции сдвига. Данный метод представляет хорошую иллюстрацию применения операции сдвига. Мы вначале задаем единичку в первом разряде шкалы, а затем в цикле сдвигаем ее, что позволяет анализировать очередное свойство объекта перечисления.
  • Данный код практически универсален и позволяет применять его для любых перечислений. К сожалению, у класса перечисления отсутствует свойство count. По этой причине для перечисления приходится явно задавать число его элементов.
Пример работы со свойствами объектов класса Person

У нас уже построен класс Person, одно из полей которого является объектом перечисления Dream_Properties. Давайте теперь посмотрим, как клиенты класса Person создают объекты этого класса, задают их свойства и осуществляют поиск персоны, обладающей нужными свойствами. Начнем с простого примера, где создается один объект класса Person и выполняются различные запросы:

/// <summary>
/// Один объект и много запросов
/// </summary>
public void TestQueries()
{
    Person person;
    Dream_Properties pattern;
    person = new Person("Петров");
    person.Properties = Dream_Properties.добрый |
         Dream_Properties.умный;
    string str = GetStrFromDreamObject(person.Properties);
    Console.WriteLine("Свойства персоны: " + str);
            
    pattern = Dream_Properties.богатый | 
         Dream_Properties.добрый;            
    str = GetStrFromDreamObject(pattern);
    Console.WriteLine("Свойства образца: " + str);

    Dream_Properties temp = person.Properties & pattern;
    bool query1, query2, query3, query4, query5;
    // все свойства образца
    query1 = temp == pattern;

    // ни одно из свойств образца
    query2 = (~person.Properties & pattern) == pattern;

   // некоторые свойства образца
   query3 = temp > 0;

   // некоторые, но не все свойства образца
   query4 = temp > 0 && temp < pattern;

   // только свойства образца
   query5 = (temp == pattern) && 
        ( (person.Properties & ~pattern) == 0);

   Console.WriteLine("результаты запросов: ");
   Console.WriteLine(" query1(все свойства) = {0}", query1);
   Console.WriteLine(" query2(ни одного из свойств) = {0}",
          query2);
   Console.WriteLine(" query3(некоторые из свойств) = {0}",
          query3);
   Console.WriteLine("query4(некоторые, но не все свойства) = {0}",
          query4);
   Console.WriteLine(" query5(только свойства образца) = {0}",
          query5);            
   }

В этом методе создается объект класса Person и образец с заданным набором свойств. Затем к объекту применяются все пять вышеописанных запросов. На рис. 3.3 показаны результаты работы метода.

 Шкалы и запросы

Рис. 3.3. Шкалы и запросы

Продолжим наш пример. В предыдущем методе к одному объекту применялись различные запросы. Чаще всего существует обратная ситуация: один запрос применяется к некоторому множеству объектов, среди которых и разыскивается объект с нужными свойствами. Рассмотрим такую ситуацию и начнем с метода, моделирующего создание массива объектов Person, каждый из которых обладает своим набором свойств.

/// <summary>
/// Создание массива Person из 5 элементов,
/// обладающих набором свойств Dream_Properties
/// </summary>
/// <returns>массив объектов Person</returns>
Person[] CreatePersonsWithProperties()
{
    Person[] persons = new Person[5];
    persons[0] = new Person("Петров");
    persons[0].Properties = Dream_Properties.умный | Dream_Properties.добрый;
    persons[1] = new Person("Фролов");
    persons[1].Properties = Dream_Properties.умный | Dream_Properties.богатый;
    persons[2] = new Person("Климов");
    persons[2].Properties = Dream_Properties.богатый | Dream_Properties.добрый;
    persons[3] = new Person("Карпов");
    persons[3].Properties = Dream_Properties.умный;
    persons[4] = new Person("Иванов");
    persons[4].Properties = Dream_Properties.добрый;
    return persons;
}

Комментировать здесь особенно нечего. Следующий метод показывает, как один запрос применяется к массиву объектов Person. В качестве результата возвращается первый найденный объект в массиве, удовлетворяющий запросу. Если же таковых объектов нет, то возвращается значение null.

/// <summary>
/// Поиск в массиве persons 
/// персоны со свойствами, заданными образцом pattern
/// </summary>
/// <param name="persons"></param>
/// <param name="pattern"></param>
/// <returns></returns>
Person FindOnePerson(Person[] persons, 
    Dream_Properties pattern)
{
    foreach (Person person in persons)
        if ((person.Properties & pattern) == pattern)                
            return person;
    return null;    
}

Для полноты картины приведу еще тестовый метод, запускающий на выполнение два приведенных выше метода.

public void TestScale()
 {
    const string NOT_EXIST =
        " К сожалению, умные, добрые и " +
        "одновременно богатые встречаются крайне редко!";
    Person[] persons = CreatePersonsWithProperties();
    Person person;
    Dream_Properties pattern = Dream_Properties.богатый |
        Dream_Properties.добрый | Dream_Properties.умный;
    string strPattern = GetStrFromDreamObject(pattern);
    Console.WriteLine ("pattern = " + strPattern); 
    person = FindOnePerson(persons, pattern);
    if (person == null)
        Console.WriteLine(NOT_EXIST);
    else 
        Console.WriteLine(person.Fam + " - " +
            GetStrFromDreamObject(person.Properties));
    pattern = Dream_Properties.добрый | Dream_Properties.умный;
    strPattern = GetStrFromDreamObject(pattern);
    Console.WriteLine("pattern = " + strPattern);
    person = FindOnePerson(persons, pattern);
    if (person == null)
        Console.WriteLine(NOT_EXIST);
    else
        Console.WriteLine(person.Fam + " - " +
            GetStrFromDreamObject(person.Properties));
 }

В этом тестовом методе к одному и тому же массиву персон применяется один и тот же запрос, но с двумя разными образцами поиска. Результаты поиска можно увидеть на рис. 3.4.

Поиск в массиве персон

Рис. 3.4. Поиск в массиве персон
< Лекция 3 || Лекция 4: 12345 || Лекция 5 >
Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дарья Федотова
Дарья Федотова
Михаил Алексеев
Михаил Алексеев
Россия, Уфа, УГАТУ, 2002
Олег Корсак
Олег Корсак
Латвия, Рига