Страницы

Поиск по вопросам

среда, 27 ноября 2019 г.

Зачем класс реализует интерфейс, который наследуется другим интерфейсом этого класса?


Просматривая исходник AutoMapper, наткнулся на интересную вещь:

Класс Mapper:

public class Mapper : IRuntimeMapper, IMapper
{
//...


Интерфейс IRuntimeMapper:

public interface IRuntimeMapper : IMapper
{
//...




Вопрос

Зачем Mapper реализует IMapper, если IRuntimeMapper уже наследует его?
Такая "ошибка" возникла в ходе расширения библиотеки или это нормальная практика? 
    


Ответы

Ответ 1



Если вы просматривали декомпилированный исходник, то это нормально. Для сравнения: Исходники BlockingCollection и описание на МСДН. JetBrains decompiler показывае состояние как в справке, несмотря на то, что сорцы явно другие. У меня есть две основных идеи, почему так. Либо информация о всех реализуемых интерфейса хранится в одном месте и не пытается строить дерево наследования каждый раз(и по этой инфе декомпилят инструменты), либо это сделано просто для удобства, чтобы не пытаться вспоминать каждый раз, а реализует ли каждый из этих интерфейсов ещё какие то.

Ответ 2



В исходниках автомаппера на гитхабе строчки public class Mapper : IRuntimeMapper, IMapper нет и никогда не было, судя по логам. Декомпиляторы могут показывать всю цепочку наследования интерфейсов интерфейсов п достаточно странной причине - наследование классов и интерфейсов в C# работает по разному. В случае иерархии классов каждый потомок имеет ровно одного явного родителя: class A {} class B : A {} class C : B {} В случае интерфейса срабатывает механизм схлопывания иерархии: A class or struct that directly implements an interface also directly implement all of the interface's base interfaces implicitly. This is true even if the class or struct doesn't explicitly list all base interfaces in the base class list. Компилятор ищет все базовые интерфейсы, строит из них полный список и дописывае их в качестве реализуемых. Так что он превращает interface A { } interface B : A { } public class SomeClass : B { } в public class SomeClass : B, A { } Именно в таком виде список унаследованных интерфейсов сохраняется в метаданных, декомпиляторы просто не могут выяснить - было ли упоминание двух интерфейсов в оригинальном коде, или их дописал компилятор.

Ответ 3



Хотя, как заметил @PashaPash строки public class Mapper : IRuntimeMapper, IMapper на GitHub никогда не было. Подобные ситуации далеко не редки. Например, стандартный класс List реализует следующие интерфейсы: public class List : IList, System.Collections.IList при этом в документации указан гораздо больший список: public class List : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable, ... причем что важно: IList наследует ICollection и IEnumerable ICollection наследует IEnumerable и IEnumerable IEnumerable наследует IEnumerable Казалось бы к чему все это, какая цель? Ответ на подобный вопрос Эрик Липперт объясняет так, когда похожий код встречается в: коде другого разработчика, автор преследует цель (на взгляд автора) сделать код проще для понимания и более самодокументированным когда дело касается документации преследуется цель предоставить максимум информации и избавить вас от головной связанной с самостоятельным выяснением цепочки наследования если речь заходит об инструментах декомпиляции, преследуется цель показать больш информации, нежели скрыть необходимую ее часть от вас. Кроме того, поскольку подобны инструменты опираются только на метаданные, а указание полного списка интерфейсов не является обязательным, инструмент может не знать, содержит ли исходный код весь список или нет. Поэтому лучше ошибиться в сторону избытка информации

Комментариев нет:

Отправить комментарий