C#. Интерфейсы

Интерфейсы




1. Для чего используются интерфейсы?

Интерфейсы определяют ряд методов (свойств, индексаторов, событий), которые должны быть реализованы в классе.

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

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

 

2. Какое отличие между интерфейсами и абстрактными классами?

В интерфейсе ни один из методов не должен иметь тела (реализации). Это означает, что в интерфейсе ни один из методов не должен иметь никакой реализации.

В абстрактном классе при описании метода тело класса (реализация класса) присутствует.

 

3. Сколько классов могут иметь реализацию методов интерфейса?

Если интерфейс определен, то он может быть реализован в любом количестве классов.

 

4. Сколько интерфейсов может быть реализовано в одном классе?

В одном классе может быть реализовано любое количество интерфейсов.

 

5. Какой общий вид описания интерфейса?

Интерфейсы объявляются с помощью ключевого слова interface. Общая форма описания интерфейса, в котором определяются методы, следующая:

interface имя
{
    возвращаемый_тип1 имя_метода1(параметры1);
    возвращаемый_тип2 имя_метода2(параметры2);
    // ...
    возвращаемый_типN имя_методаN(параметрыN);
}

где

  • имя – конкретное имя интерфейса;
  • имя_метода1, имя_метода2, …, имя_методаN – имена методов интерфейсов;
  • возвращаемый_тип1, возвращаемый_тип2, …, возвращаемый_типN – типы, которые возвращаются методами интерфейса;
  • параметры1, параметры2, …, параметрыN – списки параметров методов интерфейса.

Кроме методов, в интерфейсах можно указывать свойства, события и индексаторы.

 

6. Какие элементы языка программирования можно указывать в интерфейсах?

В интерфейсах можно указывать:

  • методы;
  • свойства;
  • индексаторы;
  • события.

 

7. Как выглядит общая форма реализации интерфейса в классе?

Общая форма реализации интерфейса в классе имеет следующий вид:

class имя_класса : имя_интерфейса
{
    // тело класса
    ...
}

где имя_интерфейса – имя интерфейса, методы (свойства, индексаторы, события) которого реализуются в классе. Класс обязательно должен реализовать все методы интерфейса.

 

8. Какая общая форма класса реализующего несколько интерфейсов?

Класс может реализовать несколько интерфейсов. В этом случае все интерфейсы определяются списком через запятую.

Общая форма класса реализующего несколько интерфейсов:

class имя_класса : имя_интерфейса1, имя_интерфейса2, ..., имя_интерфейсаN
{
    // тело класса
    ...
}

где имя_интерфейса1, имя_интерфейса2, …, имя_интерфейсаN – имена интерфейсов, которые должен реализовать класс. Класс должен реализовать все методы всех интерфейсов.

 

9. Пример объявления интерфейса и класса наследующего этот интерфейс

В данном примере интерфейсу присваивается имя IMyInterface. Рекомендовано к имени интерфейса добавить префикс ‘I’ в соответствии с общераспространенной практикой.

Интерфейс объявлен как public.

public interface IMyInterface
{
    int MyGetInt(); // метод, возвращающий число типа int
    double MyGetPi(); // метод, возвращающий число Pi
    int MySquare(int x); // метод, возвращающий x в квадрате
    double MySqrt(double x); // метод возвращающий корень квадратный из x
}

В данном примере, в интерфейсе объявлено описание четырех методов, которые должны быть реализованы во всех классах, определяющих эти интерфейсы. Это методы: MyGetInt(), MyGetPi(), MySquare(), MySqrt().

Пример описания класса использующего этот интерфейс.

public class MyClass : IMyInterface
{
    // модификатор доступа public
    public int MyGetInt()
    {
        return 25;
    }

    public double MyGetPi()
    {
        return Math.PI;
    }

    public int MySquare(int x)
    {
        return (int)(x * x);
    }

    public double MySqrt(double x)
    {
        return (double)Math.Sqrt(x);
    }
}

Все методы, которые определяются в классе, должны иметь тип доступа public. Если установить другой тип доступа (private или protected), то Visual Studio выдаст следующее сообщение:

"MyClass does not implement interface member MyFun() because it is not public."

где MyFun() – название функции, которая реализована в классе с модификатором доступа private или protected.

Это связано с тем, что в самом интерфейсе эти методы неявно считаются открытыми (public). Поэтому их реализация должна быть открытой.

 

10. Пример объявления двух интерфейсов и класса, который реализует методы этих интерфейсов

В нижеследующем примере объявлено два интерфейса с именами MyInterface и MyInterface2. Первый интерфейс содержит 4 методы. Второй интерфейс содержит 1 метод.

Также объявлен класс MyClass, использующий эти два интерфейса. Класс обязательно должен реализовать все методы обоих интерфейсов, то есть в сумме 5 методов.

public interface IMyInterface
{
    int MyGetInt(); // метод, возвращающий число типа int
    double MyGetPi(); // метод, возвращающий число Pi
    int MySquare(int x); // метод, возвращающий x в квадрате
    double MySqrt(double x); // метод, возвращающий корень квадратный из x
}


public interface IMyInterface2
{
    double MySqrt2(double x); // корень квадратный из x
}

public class MyClass : IMyInterface, IMyInterface2
{
    // методы из интерфейса MyInterface
    public int MyGetInt()
    {
        return 25;
    }

    public double MyGetPi()
    {
        return Math.PI;
    }

    public int MySquare(int x)
    {
        return (int)(x * x);
    }

    public double MySqrt(double x)
    {
        return (double)Math.Sqrt(x);
    }

    // метод из интерфейса MyInterface2
    public double MySqrt2(double x)
    {
        return (double)Math.Sqrt(x);
    }
}

 

11. Пример использования ссылки на интерфейс для доступа к методам класса

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

Пример.

public interface IMyInterface
{
    double MyGetPi(); // метод, возвращающий число Pi
}

class MyClass : IMyInterface
{
    // методы из интерфейса MyInterface
    public double MyGetPi()
    {
        return Math.PI;
    }

}

// вызов из программного кода
private void button1_Click(object sender, EventArgs e)
{
    MyClass mc = new MyClass(); // создание объекта класса mc
    IMyInterface mi; // ссылка на интерфейс
    double d;

    mi = mc; // mi ссылается на объект класса mc
    d = mi.MyGetPi(); // d = 3.14159265358979

    label1.Text = d.ToString();
}

В данном примере создается объект (экземпляр) класса MyClass с именем mc. Затем описывается ссылка на интерфейс IMyInterface с именем mi.

Строка

mi=mc;

приводит к тому, что ссылка mi указывает на объект класса mc. Таким образом, через ссылку mi можно иметь доступ к методам класса MyClass, так как класс MyClass реализует методы интерфейса IMyInterface.

С помощью ссылки на интерфейс можно иметь доступ к методам классов, которые реализуют описанные в этом интерфейсе методы.

 

12. Каким образом в интерфейсе описывается свойство?

Свойство описывается в интерфейсе без тела. Общая форма объявления интерфейсного свойства следующая:

тип имя_свойства
{
    get;
    set;
}

Если свойство предназначено только для чтения, то используется один только аксессор get.

Если свойство предназначено для записи, то используется только один аксессор set.

Пример. Описывается интерфейс и класс. Класс возвращает свойство MyPi.

public interface IMyInterface
{
    double MyGetPi(); // метод, возвращающий число Pi

    // свойство, возвращающее число Pi
    double MyPi
    {
        get;
    }
}

class MyClass : IMyInterface
{
    // метод
    public double MyGetPi()
    {
        return Math.PI;
    }

    // реализация свойства в классе
    public double MyPi
    {
        get
        {
            return Math.PI;
        }
    }
}

// использование интерфейсного свойства в обработчике события клика на кнопке
private void button1_Click(object sender, EventArgs e)
{
    MyClass mc = new MyClass(); // создание объекта класса mc

    label1.Text = mc.MyPi.ToString(); // чтение свойства
}

 

13. Пример интерфейса, в котором описывается индексатор.

Общая форма объявления интерфейсного индексатора имеет вид:

тип this[int индекс]
{
    get;
    set;
}

Пример описания и использования интерфейсного индексатора, который считывает элемент из массива, состоящего из 5 элементов типа double.

public interface IMyInterface
{
    // интерфейсный индексатор
    double this[int index]
    {
        get;
    }
}

class MyClass : IMyInterface
{
    double[] mas = { 3, 2.9, 0.5, 7, 8.3 };
    public double this[int index]
    {
        get
        {
            return mas[index];
        }
    }
}

private void button1_Click(object sender, EventArgs e)
{
    MyClass mc = new MyClass(); // создание объекта класса mc
    double d;
    d = mc[2]; // d = 0.5
    label1.Text = d.ToString();
}

 

14. Какие элементы программирования языка C# нельзя описывать в интерфейсах?

Интерфейсы не могут содержать:

  • члены данных;
  • конструкторы;
  • деструкторы;
  • операторные методы.

 

15. Как работает механизм наследования интерфейсов?

Интерфейс может наследовать другой интерфейс. Синтаксис наследования интерфейсов такой же, как и у классов.

Общая форма наследования интерфейса следующая:

interface имя_интерфейса : имя_интерфейса1, имя_интерфейса2, ..., имя_интерфейсаN
{
     // методы, свойства, индексаторы и события интерфейса
     ...
}

где имя_интерфейса – имя интерфейса, который наследует другие интерфейсы;

имя_интерфейса1, имя_интерфейса2, …, имя_интерфейсаN – имена интерфейсов-предков.

Пример. В данном примере класс MyClass использует интерфейс, который наследует другой интерфейс. В классе нужно реализовать все методы (свойства, индексаторы, события) интерфейса MyInterface1 и интерфейса MyInterface2.

// базовый интерфейс
interface MyInterface1
{
    void Int1_Meth();
}

// интерфейс, который наследует другой интерфейс
interface MyInterface2 : MyInterface1
{
    void Int2_Meth();
}

// класс, который использует интерфейс MyInterface2
class MyClass : MyInterface2
{
    // реализация метода интерфейса MyInterface1
    public void Int1_Meth()
    {
        // тело метода
        // ...
        return;
    }

    // реализация метода интерфейса MyInterface2
    public void Int2_Meth()
    {
        // тело метода
        // ...
        return;
    }
}

 

16. Что такое явная реализация члена интерфейса?

Если перед именем метода (свойства, индексатора, события) стоит имя интерфейса через разделитель ‘ . ‘ (точка), то это называется явной реализацией члена интерфейса.

Пример явной реализации.

// базовый интерфейс
interface MyInterface1
{
    void Method();
}

// класс, который реализует интерфейс MyInterface1
class MyClass : MyInterface1
{
    // явная реализация метода интерфейса MyInterface1
    void MyInterface1.Method() // указывается имя интерфейса
    {
        // тело метода
        // ...
        return;
    }
}

 

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

Явная реализация члена интерфейса применяется в следующих случаях:

  • когда нужно, чтобы интерфейсный метод был доступен по интерфейсной ссылке, а не по объекту класса, реализующего данный интерфейс. В этом случае интерфейсный метод не является открытым (public) членом класса (см. пример 1);
  • когда в одном классе реализованы два интерфейса, в которых методы имеют одинаковые имена и сигнатуру (см. пример 2).

Пример 1. Явная реализация интерфейсного метода. По интерфейсной ссылке метод есть доступен, а по объекту класса недоступен.

// Интерфейс
interface MyInterface1
{
    void Method();
}

// класс, вызывающий интерфейс
class MyClass : MyInterface1
{
    // явная реализация метода интерфейса MyInterface1
    // модификатор доступа, должен отсутствовать
    void MyInterface1.Method() // указывается имя интерфейса
    {
        // тело метода
        // ...
        return;
    }

    void InternalMethod()
    {
        MyInterface1 mi = this; // mi - интерфейсная ссылка
        mi.Method(); // работает!

        MyClass mc = this; // mc - объект класса MyClass
        // mc.Method() - невозможно вызвать - метод не открыт для объекта
    }
}

Пример 2. Есть два интерфейса MyInterface1 и MyInterface2. Каждый из них имеет методы с одинаковыми именами и сигнатурами. В данном случае это метод Method(), не возвращающий параметров (void). С помощью явной реализации класс распознает эти методы.

// интерфейс 1
interface MyInterface1
{
    void Method();
}

// интерфейс 2
interface MyInterface2
{
    void Method();
}

// класс, который использует два интерфейса
class MyClass : MyInterface1, MyInterface2
{
    // явная реализация - модификатор доступа (public) должен отсутствовать
    // метод из интерфейса MyInterface1
    void MyInterface1.Method()
    {
        // тело метода
        // ...
        return;
    }

    // метод из интерфейса MyInterface2
    void MyInterface2.Method()
    {
        // тело метода
        // ...
        return;
    }
}

 

18. В каких случаях лучше использовать интерфейс, а в каких абстрактный класс?

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

Абстрактный класс целесообразно использовать тогда, когда все же нужно уточнять некоторые детали реализации.


Связанные темы