Опубликован: 25.03.2010 | Уровень: для всех | Доступ: платный
Лекция 10:

Интерфейсы, делегаты, события в C#

Аннотация: В лекции рассматриваются интерфейсы языка С#, делегаты и способы создания событий на их основе.

Интерфейсы и делегаты

Интерфейсы

Интерфейсы - это чисто абстрактные классы, в которых все методы являются только объявлениями и не содержат реализацию. В этом случае вместо ключевого слова abstract перед объявлением класса нужно использовать ключевое слово interface. Но объявления самих методов ключевым словом abstract подтверждать не нужно и они не являются виртуальными. Интерфейсы можно рассматривать как предписание, которое нужно немедленно реализовать в ближайшем наследующем классе.

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

Интерфейсы не могут содержать других членов, кроме объявлений методов, свойств, индексаторов и событий. Запрещено включать в них поля, конструкторы и деструкторы,методы перегрузки операторов. Ни один член интерфейса не может быть объявлен статическим.

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

Для интерфейсов, в отличие от классов, язык C# допускает множественное наследование. Вслед за именем производного класса после двоеточия может следовать список наследуемых интерфейсов, разделенных запятыми. Если кроме интерфейсов наследуется еще и класс, то его имя должно возглавлять этот список, то есть базовый класс, если он есть, всегда должен стоять впереди интерфейсов.

Общепринято, но не обязательно, присваивать имена интерфейсам начинающимися с символа I, чтобы сделать их узнаваемыми в коде программы. Интерфейс просто перечисляет сигнатуру членов, которые любой наследующий его класс может реализовать по своему, при этом строго повторяя сигнатуру, заявленную в интерфейсе. Планируя использовать один и тот же интерфейс во множестве разных классов, программист как бы устанавливает общий стандарт вызовов для клиентов этих классов, скрывая от них индивидуальную для каждого класса реализацию.

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

Поскольку интерфейс относится к чисто абстрактным базовым классам, допустимо (а часто и очень удобно) объявить ссылку типа интерфейса, которая может ссылаться на любой объект, порожденный классом, наследующим (и сразу реализующим) интерфейс. Ссылка типа интерфейса имеет те же самые возможности, что и ссылка типа базового класса, которая может ссылаться на любые объекты, порожденные производными от него классами. И точно также, как в ссылках базовых классов, полномочия ссылки типа интерфейса можно изменять явным приведением типов.

Если сам интерфейс как тип объявлен с модификатором доступа public, то он доступен любому внешнему коду. Если ключевое слово public опущено или использовано ключевое слово internal, то интерфейс доступен коду только текущей сборки, в которой он объявлен. При этом наследующий класс не должен иметь больший уровень доступности, чем уровень доступности наследуемых интерфейсов.

Открытая реализация членов интерфейса

Пусть разрабатывается набор классов для реализации моделей проигрывателей от разных фирм. Клиентский код, использующий модели проигрывателей, не будет зависеть от их конкретной реализации при условии, что все функции, которые он использует, будут иметь одинаковые имена и одинаковую сигнатуру. Это обязательное условие для всех проигрывателей и устанавливает интерфейсный тип, который они наследуют.

using System;
    
namespace Test
{
    // Перечисление допустимых напряжений
    public enum StdVoltage
    {
        V110 = 110,
        V127 = 127,
        V220 = 220,
        V380 = 380
    }
    
    class Power // Электропитание
    {
        // Поле
        StdVoltage voltage = StdVoltage.V220;
        
        // Свойство
        protected StdVoltage Voltage 
        {
            get { return voltage; }
            set 
            {
                // Заменяем поле при соответствии стандарту
                switch (value)
                {
                    case StdVoltage.V110:
                    case StdVoltage.V127:
                    case StdVoltage.V220:
                    case StdVoltage.V380:
                        voltage = value;
                        break;
                }
            }
        }
    
        // Виртуальный метод
        public virtual void ShowVoltage()
        {
            // Приводим тип перечисления к целому числу
            Console.WriteLine("Питание {0} Вольт", 
    (int)voltage);
        }
    }
    
    interface IPlayer
    {
        // Все объявления по умолчанию public
        void Start();
        void Stop();
        void Pause();
    }
    
    class Panasonic : Power, IPlayer
    {
        public void Start()
        {
            Console.WriteLine("Panasonic: 
    Реализация Start");
        }
    
        public void Stop()
        {
            Console.WriteLine("Panasonic: 
    Реализация Stop");
        }
    
        public void Pause()
        {
            Console.WriteLine("Panasonic: 
    Реализация Pause");
        }
    
        public override void ShowVoltage()
        {
            Voltage = StdVoltage.V110;
            Console.Write("Panasonic: ");
            base.ShowVoltage();
        }
    }
    
    class Sony : Power, IPlayer
    {
        public void Start()
        {
            Console.WriteLine("Sony: 
    Реализация Start");
        }
    
        public void Stop()
        {
            Console.WriteLine("Sony: 
    Реализация Stop");
        }
    
        public void Pause()
        {
            Console.WriteLine("Sony: 
    Реализация Pause");
        }
    
        public override void ShowVoltage()
        {
            Voltage = StdVoltage.V127;
            Console.Write("Sony: ");
            base.ShowVoltage(); 
        }
    }
    
    class Samsung : Power, IPlayer
    {
        public void Start()
        {
            Console.WriteLine("Samsung: 
    Реализация Start");
        }
    
        public void Stop()
        {
            Console.WriteLine("Samsung: 
    Реализация Stop");
        }
    
        public void Pause()
        {
            Console.WriteLine("Samsung: 
    Реализация Pause");
        }
    
        // Здесь мы не переопределяли виртуальную функцию ShowVoltage(),
        // поэтому при использовании экземплярной ссылки будет
        // вызвана ее версия из слоя базового класса с начальным значением 220V
    }
    
    // Вызывающий клиентский код
    class MyClass
    {
        public MyClass()
        {
            // Создаем производные объекты, адресуемые
            // ссылками типа наследуемого интерфейса
            IPlayer[] refInterface = 
                {
                    new Panasonic(),
                    new Sony(),
                    new Samsung()
                };
    
            // Выполняем действия, реализованные индивидуально
            for(int i = 0; i < refInterface.Length; i++)
            {
            refInterface[i].Start();
            refInterface[i].Stop();
            refInterface[i].Pause();
            }
            Console.WriteLine();
    
            // Печатаем индивидуальное питание
            // Для этого поднимаем полномочия ссылки без 
            // проверки, содержится ли в адресуемом объекте 
            // нужный слой (мы-то знаем, что содержится)
            ((Panasonic)refInterface[0]).ShowVoltage();
            ((Sony)refInterface[1]).ShowVoltage();
            // Вызываем виртуальную функцию из слоя базового класса
            // Предварительно на всякий случай проверяем,
            // есть ли в объекте, адресуемом ссылкой типа IPlayer,
            // слой Samsung, чтобы безопасно вызывать его члены
            if (refInterface[2] is Samsung)
            {
                Samsung samsung = (Samsung)refInterface[2];
                Console.Write("Samsung: ");
                // Функции ShowVoltage() в слое Samsung нет, поэтому 
                // будет вызвана версия из слоя базового класса Power
                samsung.ShowVoltage();
            }
        }
    }
    
    // Запуск
    class Program
    {
        static void Main()
        {
            // Настройка консоли
            Console.Title = "Использование интерфейсов";
            Console.ForegroundColor = ConsoleColor.White;
            Console.CursorVisible = false;
            Console.WindowWidth = 60;
            Console.WindowHeight = 10;
    
            new MyClass();// Чтобы сработал конструктор
    
            Console.ReadLine();
        }
    }
}
Листинг 10.1 . Открытая реализация членов интерфейсов

Результат выполнения примера будет таким


Максим Филатов
Максим Филатов

Прошел курс. Получил код Dreamspark. Ввожу код на сайте, пишет:

Срок действия этого кода проверки уже истек. Проверьте, правильно ли введен код. У вас осталось две попытки. Вы также можете выбрать другой способ проверки или предоставить соответствующие документы, подтверждающие ваш академический статус.

 

Как активировать код?

Денис Пашков
Денис Пашков
Россия
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974