Это сложно.
Вызов b.Clone явно должен вызывать BC. Здесь вообще нет интерфейса! Метод для вызова определяется полностью анализом во время компиляции. Поэтому он должен возвращать экземпляр Base. Этот не очень интересный.
В отличие от этого, вызов cb.Clone чрезвычайно интересен.
Есть две вещи, которые мы должны установить, чтобы объяснить поведение. Первый: какой «слот» вызывается? Второе: какой метод в этом слоте?
Экземпляр Derived должен иметь два слота, поскольку необходимо реализовать два метода: ICloneable<Derived>.Clone
и ICloneable<Base>.Clone
. Давайте назовем эти слоты ICDC и ICBC.
Очевидно, что слот, который вызывается cb.Clone, должен быть слотом ICBC; у компилятора нет причин знать, что слот ICDC существует даже в cb типа ICloneable<Base>
.
Какой метод идет в этот слот? Есть два метода, Base.Clone и Derived.Clone. Давайте назовем эти BC и DC. Как вы обнаружили, содержимое этого слота в экземпляре Derived является DC.
Это кажется странным. Ясно, что содержимое слота ICDC должно быть DC, но почему содержимое слота ICBC также должно быть DC? Есть ли в спецификации C # что-нибудь, оправдывающее такое поведение?
Наиболее близким является раздел 13.4.6, в котором говорится о «повторной реализации интерфейса». Вкратце, когда вы говорите:
class B : IFoo
{
...
}
class D : B, IFoo
{
...
}
тогда, что касается методов IFoo, мы начинаем с нуля в D . Все, что B должен сказать о том, какие методы B отображаются на методы IFoo, отбрасывается; D может выбрать те же отображения, что и B, или выбрать совершенно другие. Такое поведение может привести к непредвиденным ситуациям; Вы можете прочитать больше о них здесь:
http://blogs.msdn.com/b/ericlippert/archive/2011/12/08/so-many-interfaces-part-two.aspx
Но: является ли реализация ICloneable<Derived>
a повторной реализацией из ICloneable<Base>
? Не совсем понятно, что так и должно быть. Повторная реализация интерфейса IFoo представляет собой повторную реализацию каждого базового интерфейса IFoo, но ICloneable<Base>
не является базовым интерфейсом из ICloneable<Derived>
!
Сказать, что это повторная реализация интерфейса, конечно, было бы неестественно; спецификация не оправдывает его.
Так что здесь происходит?
Здесь происходит то, что среда выполнения должна заполнить слот ICBC. (Как мы уже говорили, слот ICDC явно должен получать метод DC.) Среда выполнения думает, что это является повторной реализацией интерфейса, поэтому он делает это путем поиска из Derived в Base и выполняет первый подходит матч. DC является совпадением благодаря дисперсии, поэтому он выигрывает у BC.
Теперь вы можете спросить, где , что поведение указано в спецификации CLI, и ответ "нигде". На самом деле ситуация значительно хуже; тщательное чтение спецификации CLI фактически показывает, что задано поведение противоположное . Технически CLR здесь не соответствует собственной спецификации.
Однако рассмотрим точный случай, который вы здесь описали. Разумно предположить, что кто-то, кто вызывает ICloneable<Base>.Clone()
на экземпляре Derived, хочет вернуть Derived обратно!
Когда мы добавили дисперсию в C #, мы, конечно, протестировали сам сценарий, который вы упомянули здесь, и в конечном итоге обнаружили, что поведение было неоправданным и желательным. Затем последовал период некоторых переговоров с хранителями спецификации CLI относительно того, следует ли нам редактировать спецификацию так, чтобы это желательное поведение было оправдано спецификацией. Я не помню, каковы были результаты этих переговоров; Я не был лично вовлечен в это.
Итак, подведем итог:
- Де-факто , CLR выполняет поиск соответствия первой строки от производного до базового, как если бы это была повторная реализация интерфейса.
- De jure , это не оправдано ни спецификацией C #, ни спецификацией CLI.
- Мы не можем изменить реализацию, не разбив людей.
- Реализация интерфейсов, которые объединяют при преобразованиях с отклонениями, опасна и сбивает с толку;попытайтесь избежать этого.
Еще один пример того, как вариант унификации интерфейса демонстрирует необоснованное, зависящее от реализации поведение в реализации CLR «первая подгонка», см.http://blogs.msdn.com/b/ericlippert/archive/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity.aspx
И для примера, в котором не вариантное универсальное объединение методов интерфейса представляет необоснованное, зависящее от реализации поведение в реализации CLR «первое соответствие», см .:
http://blogs.msdn.com/b/ericlippert/archive/2006/04/05/odious-ambiguous-overloads-part-one.aspx
В этом случае вы можете вызвать изменение поведения программы, переупорядочив текст программы, что действительно странно в C #.