#c_sharp #ооп #наследование
У меня есть один обобщенный интерфейс, от которого наследованы несколько интерфейсов. Так же имеется конструктор который принимает интерфейс типа IVLCObject, т.к. он использует обобщенный интерфейс, и я могу из него извлечь необходимый указатель на неуправляемый объект, встал вопрос об уменьшении вызовов с разными типами, и подмена объекта на обобщенный интерфейс. На данный момент я подумал что можно так проверить на факт присутствия интерфейса в объекте. Как правильно проверить наследован ли объект обобщенного интерфейса от IVLCMedia? Обобщенный интерфейс: public interface IVLCObject : IDisposable { IntPtr ObjectHandle { get; } bool IsInvalid { get; } } 2 интерфейса наследника: public interface IVLCMedia : IVLCObject { Uri Location { get; } void AddOption(string mediaOption); long Duration { get; } ... } public interface IVLCMediaPlayer : IVLCObject { IVLCMedia Media { get; set; } bool Play(); void Pause(); void Stop(); ... } А так же конструктор класса принимающий такой интерфейс: internal class VLCMediaPlayer : VLCObject, IVLCMediaPlayer { public VLCMediaPlayer(IVLCObject vlcObject) : base(libvlc_media_player_new(vlcObject, vlcObject.GetType().GetInterface("IVLCMedia") != null), libvlc_media_player_release) { } .... private static IntPtr libvlc_media_player_new(IVLCObject vlcObject, bool fromMedia = false) { return Environment.Is64BitProcess ? fromMedia ? libvlc_media_player_new_from_media64(vlcObject.ObjectHandle) : libvlc_media_player_new64(vlcObject.ObjectHandle) : fromMedia ? libvlc_media_player_new_from_media32(vlcObject.ObjectHandle) : libvlc_media_player_new32(vlcObject.ObjectHandle); } }
Ответы
Ответ 1
Итак, давайте посмотрим, какие варианты решения Вашей проблемы имеются в арсенале .NET: #0: Type.GetInterface В своем коде Вы использовали метод Type.GetInterface(string), так что логично начать именно с него public static bool Implements(this object Object) => Object.GetType().GetInterface(typeof(UInterface).Name) != null; Судя по одной из реализаций данного метода, под катом чаще всего все равно сокрыто итерирование по всем наследуемым интерфейсам с последующей проверкой на соответствие условию (в нашем случае - полное совпадение имени) Так что отсюда логичным будет перейти к следующему варианту: #1: Type.GetInterfaces Мы можем получить список реализуемых нашим объектом интерфейсов и пройтись по нему сами, используя метод Type.GetInterfaces(): public static bool Implements (this object Object) { Type interfaceType = typeof(UInterface); return Object.GetType().GetInterfaces().Contains(interfaceType); } На этом, думаю, хватит эксплуатировать идею с проходом по списку всех интерфейсов, так что попробуем нечто иное: #2: Pattern matching Как вариант, мы всегда можем проверить возможность приведения объекта к нужному нам типу интерфейса с помощью сопоставления шаблонов, так что метод можно переписать вот так: public static bool Implements (this object Object) => Object is UInterface; Код стал раз в 10 элегантнее, чем все то, что мы до сих пор видели выше Но так ли хорош этот код в плане быстроты исполнения? Увидим ниже) #3: Type.IsAssignableFrom По аналогии с сопоставлением шаблонов, мы можем проверить, можно ли присвоить экземпляр указанного типа экземпляру текущего типа с помощью метода Type.IsAssignableFrom(Type): public static bool Implements (this object Object) => typeof(UInterface).IsAssignableFrom(Object.GetType()); Это может быть особенно полезно в тех случаях, когда на момент компиляции тип интерфейса нам неизвестен (о сопоставлении шаблонов при таком раскладе, понятное дело, речи не идет) #4: Type.IsInstanceOfType Как мне кажется, метод Type.IsInstanceOfType(Object), используемый в этом абзаце, имеет не совсем верное название (я бы ожидал от него строгой проверки соответствия типов), однако результат его исполнения показывает входит ли текущий объект Type в иерархию наследования объекта, представленного параметром, или является ли текущий объект Type интерфейсом, реализуемым параметром А сие как раз нам и нужно, так что в очередной раз перепишем метод: public static bool Implements (this object Object) => typeof(UInterface).IsInstanceOfType(Object); #5: Здравствуйте, я ваша тетя таблица методов Ну не был бы я собой, если бы не добавил к ответу что-нибудь эдакое! Пока я вчера писал этот пост, у меня формировалась следующего рода идея: информация обо всех реализуемых интерфейсах лежит прямиком в виртуальной таблице методов объекта. Так почему бы не попробовать вытянуть ее прямиком оттуда? Собственно, это я и сделал) Для начала стоит разобрать представление .NET-объекта в памяти На сей счет в интернете я нашел следующую прекрасную картинку, которая проиллюстрирует сие Вам как нельзя лучше: (Если Вы первоначальный автор этой картинки, то я ни коим образом не претендую на ее авторство. Она лишь служит той цели, которую Вы в нее и заложили: просвещает людей) Что же тут происходит? В первом столбике мы видим представление самого объекта в памяти. К слову, указатель на объект приводит нас прямиком к ссылке на таблицу методов В таблице методов нас интересует графа "Pointer to Interfaces", которая направит нас прямо к сектору, где записаны runtime-указатели всех реализуемых объектом интерфейсов Список интерфейсов идет до начала списка, собственно, методов, присущих объекту. Так что интересующая нас зона располагается ровно до него. Адрес начала списка методов можно достать из графы "Pointer to Methods" Каждая графа занимает по 4 байта, так что спокойно может быть интерпретирована как IntPtr Думаю, разобрались! Так что вот Вам код реализации поиска искомого интерфейса в таблице методов объекта: public static bool Implements (this object Obj) { unsafe { // Получим MT Pointer нашего интерфейса IntPtr iMTPointer = typeof(UInterface).TypeHandle.Value; // Получим ссылку на объект TypedReference trObj = __makeref(Obj); IntPtr ptrObj = **(IntPtr**)&trObj; // Получим ссылку на таблицу методов объекта void* methodTable = (*(IntPtr*)ptrObj.ToPointer()).ToPointer(); // Перейдем к разделу реализуемых интерфейсов IntPtr* interfaces = (IntPtr*)((IntPtr*)methodTable)[9].ToPointer(); // А также получим число реализуемых интерфейсов int count = ((ushort*)methodTable)[7]; for (int i = 0; i < count; ++interfaces, ++i) // Разыменовываем текущий указатель и сверяем, // равно ли его значение искомому if (*interfaces == iMTPointer) return true; // Если в цикле ничего найдено не было, возвращаем false return false; } } Это работает и работает на удивление быстро! Понятное дело, что тягаться с is из метода #2 никто не сможет (он на уровне IL представлен всего одной командой), однако сей метод выигрывает по скорости у всех остальных! Подробнее - ниже На сием методы (коие мне известны) заканчиваются, так что подведем итоги, прогнав все это дело через benchmark: | Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | |--------------------- |-----------:|-----------:|-----------:|-----------:|------:|--------:|-----:| | #0 CompileTimeString | 379.808 ns | 7.3281 ns | 7.5254 ns | 380.619 ns | 1.00 | 0.00 | 6 | | #0 RunTimeString | 428.352 ns | 12.5997 ns | 36.7538 ns | 419.475 ns | 1.15 | 0.08 | 7 | | #1 | 200.056 ns | 5.1834 ns | 14.8722 ns | 191.448 ns | 0.53 | 0.05 | 5 | | #2 | 8.363 ns | 0.0859 ns | 0.0803 ns | 8.321 ns | 0.02 | 0.00 | 1 | | #3 | 62.209 ns | 0.6735 ns | 0.6300 ns | 62.278 ns | 0.16 | 0.00 | 4 | | #4 | 41.878 ns | 0.3018 ns | 0.2675 ns | 41.813 ns | 0.11 | 0.00 | 3 | | #5 | 16.117 ns | 0.2061 ns | 0.1827 ns | 16.001 ns | 0.04 | 0.00 | 2 | P.S. - В таблице значение графы Method соответствует номеру метода в данном ответе (метод #0 был протестирован отдельно для строки с именем, которая задана на момент компиляции, а также для той, которая получается непосредственно из типа интерфейса) Итак, волею судеб получилось так, что Вы случайно избрали самый медленный из возможных вариантов, так что вопрос Вы задали не зря :D Как очень наглядно видно из таблицы, самым лучшем решением является сопоставление шаблонов, которое отрабатывает почти в 50(!) раз быстрее, нежели вариант под номером 0! Так что, возвращаясь к вопросу "так ли хорош этот код в плане быстроты исполнения", отвечу: - Да, так же хорош, как и прост в написании Вывод из всего, что было сказано выше, прост: Если у Вас уже есть объект и Вы просто хотите проверить, наследует ли он желаемый интерфейс (известный на момент компиляции) - используйте сопоставление шаблонов (#2) Если же тип неизвестен, но у Вас по-прежнему на руках есть объект, то используйте мой метод (#5) или метод Type.IsInstanceOfType(Object) (#4) Ну а если Вы не оперируете объектом и/или тип интерфейса заранее не удосужился стать известным, то метод Type.IsAssignableFrom(Type) (#3) - Ваш верный выбор! Надеюсь, данный ответ помог Вам разрешить Вашу проблему! Удачи в Ваших свершениях!
Комментариев нет:
Отправить комментарий