C #: переопределение свойств путем явного указания интерфейса - PullRequest
16 голосов
/ 14 сентября 2010

Пытаясь переопределить явную реализацию интерфейса свойства ICollection<T>.IsReadOnly из класса Collection<T>, я натолкнулся на некоторые документы, утверждающие, что явные реализации членов интерфейса не могут быть переопределены, поскольку они не могут иметь модификаторы, такие как virtual или abstract.На MSDN они даже зашли так далеко, что указали, как сделать явную реализацию элемента интерфейса доступной для наследования, создав еще один абстрактный или виртуальный член, который вызывается явной реализацией члена интерфейса.Пока проблем нет.

Но тогда мне интересно: Почему в C # возможно переопределить любой явно реализованный элемент интерфейса, просто указав интерфейс явно ?

Например, предположим, что у меня есть такой простой интерфейс со свойством и методом:

public interface IMyInterface
{
    bool AlwaysFalse { get; }
    bool IsTrue(bool value);
}

И класс A, который явно реализует интерфейс, иимеет метод Test(), который вызывает собственную реализацию члена интерфейса.

public class A : IMyInterface
{
    bool IMyInterface.AlwaysFalse
    { get { return false; } }

    bool IMyInterface.IsTrue(bool value)
    { return value; }

    public bool Test()
    { return ((IMyInterface)this).AlwaysFalse; }
}

Как видите, ни один из четырех членов не является виртуальным или абстрактным, поэтому, когда я определяю класс B, например, так:

public class B : A
{
    public bool AlwaysFalse
    { get { return true; } }

    public bool IsTrue(bool value)
    { return !value; }
}

Тогда вы ожидаете, что экземпляр B приведёт к A и будет вести себя как A.И это делает:

A a = new A();
Console.WriteLine(((IMyInterface)a).AlwaysFalse);    // False
Console.WriteLine(((IMyInterface)a).IsTrue(false));  // False
Console.WriteLine(a.Test());                         // False
A b = new B();
Console.WriteLine(((IMyInterface)b).AlwaysFalse);    // False
Console.WriteLine(((IMyInterface)b).IsTrue(false));  // False
Console.WriteLine(b.Test());                         // False

Теперь приходит улов.Создайте класс C, который является точной копией B, за исключением одной вещи в объявлении класса:

public class C : A, IMyInterface
{ /* ... same as B ... */ }

Теперь экземпляр C при приведении к A не делаетведет себя как A, но как C:

A c = new C();
Console.WriteLine(((IMyInterface)c).AlwaysFalse);    // True
Console.WriteLine(((IMyInterface)c).IsTrue(false));  // True
Console.WriteLine(c.Test());                         // True

Даже метод Test() теперь вызывает переопределенный метод в C!Почему это?

1 Ответ

10 голосов
/ 14 сентября 2010

Это не имеет ничего общего с явной реализацией интерфейса; это просто следствие общих правил наследования и отображения интерфейса: вы бы увидели точно те же результаты, если бы тип A предоставлял неявную, а не явную реализацию IMyInterface.

  • Тип B наследуется от типа A. Ничто не отменяется.
    B предоставляет свои AlwaysFalse и IsTrue члены, но они не реализуют IMyInterface; Реализация IMyInterface обеспечивается членами, унаследованными от A: когда экземпляр типа B приводится к IMyInterface, он ведет себя точно так же, как экземпляр типа A, поскольку A предоставляет членов, которые реализуют интерфейс.
  • Тип C наследуется от типа A. Опять же, ничего не отменяется.
    C предоставляет свои собственные члены AlwaysFalse и IsTrue, но на этот раз эти члены do реализуют IMyInterface: когда экземпляр типа C приводится к IMyInterface, тогда члены C обеспечивают реализацию интерфейса, а не A.

Поскольку тип A явно реализует IMyInterface, компилятор не предупреждает, что члены B и C скрывают элементы A; В действительности эти члены A уже были скрыты из-за явной реализации интерфейса.

Если вы изменили тип A для реализации IMyInterface неявным, а явным образом, то компилятор предупредит, что члены B и C скрывают, а не переопределяют элементы A и что вы в идеале следует использовать модификатор new при объявлении этих членов в B и C.

Вот некоторые важные биты из спецификации языка. (Разделы 20.4.2 и 20.4.4 в спецификации ECMA-334 ; разделы 13.4.4 и 13.4.6 в спецификации Microsoft C # 4 .)

20.4.2 Отображение интерфейса

Реализация конкретного элемент интерфейса I.M, где I интерфейс, в котором член M объявлен, определяется изучение каждого класса или структуры S, начиная с C и повторяя для каждый последующий базовый класс C, до совпадения.

20.4.4 Повторная реализация интерфейса

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

...