Страницы

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

суббота, 7 декабря 2019 г.

Кастинг массива к IReadOnlyCollection<T>

#c_sharp #net


Почему нижеследующий код работает?

int[] ixs = new int[10];
int cnt = ((IReadOnlyCollection)ixs).Count;


Как это вообще компилируется? Ведь у массива нет свойства Count (у него есть Length
и LongLength), соответственно - он не должен приводиться к IReadOnlyCollection.
    


Ответы

Ответ 1



Свойства Count у массива и правда нету. Но для того, чтобы имплементировать интерфейс, оно и не нужно. Есть такая штука в .NET — explicit interface implementation. Это когда класс имплементирует интерфейс, но не заводит метод с нужным именем. (Она и применяется в случае массива.) Пример. Пусть у нас есть такой интерфейс: interface I { void Run(); } Тогда мы можем сделать так: class C : I { public void Run() { ... } } А можем и так: class C : I { void I.Run() { ... } } При этом интерфейс имплементирован в обоих случаях, но в первом случае у нас есть публичный метод Run, а во втором нету, и этот метод можно вызвать только через каст к нужному интерфейсу. Для чего такая фича языка (explicit interface implementation) реально нужна? А вот для чего. Представьте себе, что вы хотите реализовать в классе два интерфейса, которые конфликтуют между собой, определяют функцию или свойство с одинаковым именем, которые нужно для разных интерфейсов реализовывать по-разному. Тогда вы можете написать две разные имплементации: class C : IExecutioner, IAsyncModelObject { Task IExecutioner.Execute() { /* казним приговорённого */ } Task IAsyncModelObject.Execute() { /* начинаем работу в фоне */ } } Любой массив в .NET произведён от System.Array. Самой имплементации конкретных массивов в исходниках фреймворка нету, она генерируется автоматически. Документация говорит: Starting with the .NET Framework 2.0, the Array class implements the System.Collections.Generic.IList, System.Collections.Generic.ICollection, and System.Collections.Generic.IEnumerable generic interfaces. The implementations are provided to arrays at run time, and as a result, the generic interfaces do not appear in the declaration syntax for the Array class. В реальности, начиная с .NET 4.5, добавлены тем же механизмом ещё два интерфейса: System.Collections.Generic.IReadOnlyList and System.Collections.Generic.IReadOnlyCollection. Все эти интерфейсы реализует не класс Array, а производные от него классы, представляющие собой массивы конкретного типа. Посмотрим интерфейсы, реализуемые типом System.Array и конкретным типом массива int[]. Такой код: foreach (var i in typeof(System.Array).GetInterfaces()) Console.WriteLine(i); выдаёт System.ICloneable System.Collections.IList System.Collections.ICollection System.Collections.IEnumerable System.Collections.IStructuralComparable System.Collections.IStructuralEquatable В то же время foreach (var i in typeof(int[]).GetInterfaces()) Console.WriteLine(i); выдаёт System.ICloneable System.Collections.IList System.Collections.ICollection System.Collections.IEnumerable System.Collections.IStructuralComparable System.Collections.IStructuralEquatable System.Collections.Generic.IList`1[System.Int32] System.Collections.Generic.ICollection`1[System.Int32] System.Collections.Generic.IEnumerable`1[System.Int32] System.Collections.Generic.IReadOnlyList`1[System.Int32] System.Collections.Generic.IReadOnlyCollection`1[System.Int32] Итак, лишь конкретные массивы имплементируют IReadOnlyCollection, сам System.Array этого не делает. Окей, но откуда же берётся сам код свойства? Заглянем в исходники .NET (Array -- базовый класс всех массивов) видим: public abstract class Array : ICloneable, IList, IStructuralComparable, IStructuralEquatable { // ... int ICollection.Count { get { return Length; } } Таким образом, свойства Count действительно нет, есть лишь свойство ICollection.Count, которое доступно только через каст к интерфейсу. Это показывает, откуда взялась реализация ICollection, но что же насчёт IReadOnlyCollection? Как правильно отмечает @andreycha в соседнем ответе, для реализации generic-интерфейсов используется немного компиляторной магии и класс SZArrayHelper. Из исходников: // This class is needed to allow an SZ array of type T[] to expose IList, // IList, etc., etc. all the way up to IList. When the following call is // made: // // ((IList) (new U[n])).SomeIListMethod() // // the interface stub dispatcher treats this as a special case, loads up SZArrayHelper, // finds the corresponding generic method (matched simply by method name), instantiates // it for type and executes it. Перевод: // Этот класс (SZArrayHelper) нужен, чтобы позволить массиву T[] имплементировать // IList, IList и т. д. аж до IList. если вызывается // // ((IList) (new U[n])).МетодИзIList() // // код, отвечающий за нахождение интерфейсов в классе считает это особым случаем, // загружает SZArrayHelper, находит соответствующий обобщённый метод (просто по имени) // инстанциирует его для нужного типа T и выполняет. Это значит, что каст работает, но код реально находится в SZArrayHelper, и то, что этот код используется, гарантируется специальными трюками рантайма. Реальное место нахождения свойства Count — SZArrayHelper.get_Count.

Ответ 2



он не должен приводиться к IReadOnlyCollection Заглянем в исходники: Note that we make a T[] (single-dimensional w/ zero as the lower bound) implement both IList and IReadOnlyList, where T : U dynamically. See the SZArrayHelper class for details. Что говорит о том, что массив неявным образом имплементирует IList<> и IReadOnlyList<> (очевидно, какая-то внутренняя магия, коей в дотнете хватает). IReadOnlyList<> в свою очередь имплементирует IReadOnlyCollection<>. Поэтому в вашем коде приведение срабатывает корректно. А про неявную имплементацию вам уже ответил VladD.

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

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