Шаг 162.
Язык программирования C#. Начала.
Абстрактные классы и интерфейсы. Явная реализация членов интерфейса (окончание)

    На этом шаге мы рассмотрим особенности явной реализации членов интерфейса.

    Но у нас есть и альтернатива реализации предыдущего шага. Мы можем воспользоваться явной реализацией методов, свойств и индексаторов интерфейса. При явной реализации, когда соответствующий член описывается в классе, перед его именем (через точку) указывается название интерфейса, а спецификатор уровня доступа не указывается. Доступ к такому члену класса можно получить через переменную соответствующего интерфейса. Дальше обратимся к примеру. Программа представлена ниже.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr162_1
{
    // Базовый класс: 
    abstract class Base {
        // Абстрактное свойство: 
        public abstract char symbol { 
            get;
        }
        // Абстрактный индексатор: 
        public abstract int this[int k] { 
            get;
        }
        // Абстрактный метод: 
        public abstract void show(); 
    }

    // Первый интерфейс: 
    interface First {
        // Свойство: 
        char symbol { 
            get;
        }
        // Индексатор: 
        int this[int k] { 
            get;
        }
        // Метод: 
        void show();
    }

    // Второй интерфейс: 
    interface Second {
        // Свойство: 
        char symbol {
            get;
        }
        // Индексатор: 
        int this[int k] { 
            get;
        }
        // Метод: 
        void show();
    }

    // Производный класс наследует абстрактный базовый класс 
    // и реализует интерфейсы: 
    class MyClass: Base, First, Second {
        // Закрытое символьное поле: 
        private char smb;
        // Конструктор с символьньм аргументом: 
        public MyClass(char s): base() { 
            smb = s;
        }
        // Описание свойства из абстрактного класса: 
        public override char symbol { 
            get {
                return smb;
            }
        }
        // Явная реализация свойства из интерфейса First: 
        char First.symbol { 
            get {
                return (char)(smb + 1);
            }
        }
        // Описание индексатора из базового класса: 
        public override int this[int k] {
            get {
                return smb + k;
            }
        }
        // Явная реализация индексатора из интерфейса Second: 
        int Second.this[int k] { 
            get {
                return smb - k;
            }
        }
        // Описание метода из базового класса: 
        public override void show() {
            Console.WriteLine("Базовый класс Base:\t\'{0}\'", symbol);
        }
        // Явная реализация метода из интерфейса First: 
        void First.show() {
            Console.WriteLine("Интерфейс First:\t\'{0}\'", symbol);
        }
        // Явная реализация метода из интерфейса Second: 
        void Second.show() {
            Console.WriteLine("Интерфейс Second:\t\'{0}\'", symbol);
        }
    }

    // Класс с главным методом: 
    class Program
    {
        // Главный метод:
        static void Main()
        {
            // Создание объекта:
            MyClass obj = new MyClass('A');
            // Интерфейсные переменные:
            First A = obj;
            Second = obj;
            // Вызов метода через переменные: 
            obj.show();
            A.show();
            B.show();
            // Считывание значения свойства: 
            Console.WriteLine("obj.symbol = \'{0}\'", obj.symbol);
            Console.WriteLine("  A.symbol = \'{0}\'", A.symbol);
            Console.WriteLine("  B.symbol = \'{0}\'", B.symbol);
            // Индексирование объекта:
            Console.WriteLine("obj[10] = {0}", obj[10]);
            Console.WriteLine("  A[10] = {0}", A[10]);
            Console.WriteLine("  B[10] = {0}", B[10]);
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

    Результат выполнения программы следующий:


Рис.1. Результат выполнения программы

    Интерфейсы First и Second описаны одинаково. Отличает их только название. В каждом из интерфейсов объявлено символьное свойство symbol с get-аксессором, индексатор с целочисленным индексом и get-аксессором и метод show() - без аргументов и не возвращающий результат. Такая же "начинка" и у абстрактного класса Base, но только с поправкой на использование в описании соответствующих членов ключевых слов public и abstract. Класс MyClass наследует класс Base и реализует интерфейсы First и Second. В этом классе появляется закрытое символьное поле smb и конструктор с одним символьным аргументом, который определяет значение поля. Но нас, конечно, в первую очередь интересует тот способ, которым описывается метод show(), свойство symbol и индексатор.

    Свойство symbol описано дважды. Одно из описаний свойства - это обычное описание унаследованного из абстрактного класса свойства. При запросе значения свойства результатом возвращается значение поля smb. Также в классе есть явная специализация для свойства из интерфейса First. Данная версия описана без ключевого слова public, а название свойства указано в виде First.symbol. Значение свойства вычисляется как (char)(smb+1). Это следующий символ после символа, записанного в поле smb (но значение самого поля smb при этом не меняется). Такая версия свойства будет задействована, если мы будем обращаться к объекту класса MyClass через интерфейсную переменную типа First. Если мы будем получать доступ к объекту через объектную переменную класса MyClass или интерфейсную переменную типа Second, то используется первая версия свойства.

    У индексатора также две версии. Одна описана как свойство, переопределяемое в производном классе. При запросе значения выражения с проиндексированным объектом при заданном индексе k результатом возвращается значение smb+k (сумма кода символа из поля smb и индекса k). Явная реализация индексатора выполняется для интерфейса Second. Версия описывается без ключевого слова public, а вместо ключевого слова this используется конструкция Second.this. Запрос значения выражения с проиндексированным объектом вычисляется как разность кода символа из поля smb и индекса k (выражение smb-k). Явная реализация индексатора будет задействована, если мы станем индексировать объект через интерфейсную переменную типа Second. При индексировании объекта через объектную переменную класса MyClass или через интерфейсную переменную типа First в игру вступает первая версия индексатора.

    У метода show() в классе MyClass есть три версии. Версия с ключевым словом override вызывается через объектную переменную базового класса. Явная реализация метода из интерфейса First задействуется при обращении к объекту через переменную интерфейсного типа First. При обращении к объекту через интерфейсную переменную типа Second используется явная реализация метода для интерфейса Second. Каждая версия метода отображает значение свойства symbol объекта и название класса или интерфейса, для которого выполнена реализация метода.


Вообще общая концепция использования явных реализаций методов, свойств и индексаторов интерфейса немного другая. Мы можем для интерфейса выполнить явную реализацию метода, свойства, индексатора. И если обращение к объекту выполняется через интерфейсную переменную соответствующего типа, то используется данная явная реализация для метода, свойства, индексатора. Во всех прочих случаях используются "обычные" реализации (или неявные реализации - для них не указывается имя интерфейса). Что касается метода show(), то для него определены явные реализации для интерфейсов First и Second. Первая используется, если доступ к объекту выполняется через ссылку типа First, а вторая - при использовании переменной типа Second. Во всех прочих случаях используется версия, переопределяющая абстрактный метод из класса Base. Для нас "все прочие случаи" - это обращение к объекту через объектную переменную класса MyClass.

    В главном методе командой

  MyClass obj = new MyClass('A');
создается объект класса MyClass. Командами
  First A = obj;
и
  Second = obj;
ссылка на этот объект записывается в интерфейсные переменные A и B. Переменные A, B и obj мы используем для вызова метода show() (команды
  obj.show(); 
  A.show(); 
и
  B.show();
), считывания значения свойства symbol (инструкции obj.symbol, A.symbol и B.symbol) и индексирования объектов (инструкции obj[10], A[10] и B[10]). Верится, что особых пояснений результат выполнения программы не требует. Но есть одно обстоятельство, связанное с методом show(). В этом методе выполняется обращение к свойству symbol. Но через какую бы переменную мы ни вызывали метод, всегда используется "общая" версия свойства, поэтому во всех трех случаях значение свойства равно 'A' (значение аргумента, переданного конструктору класса MyClass при создании объекта). А вот когда мы обращаемся к свойству через переменную A, то получаем значение 'B', поскольку здесь задействована явная реализация для данного свойства.

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




Предыдущий шаг Содержание Следующий шаг