Почему не является массивом общего типа?

Array объявляется следующим образом:

 public abstract class Array : ICloneable, IList, ICollection, IEnumerable { 

Мне интересно, почему это не так:

 public partial class Array : ICloneable, IList, ICollection, IEnumerable { 
  1. Что было бы проблемой, если бы он был объявлен родовым типом?

  2. Если бы это был общий тип, нам все еще нужен не общий? И если бы мы это сделали, можно ли это просто получить из Array (как предполагалось выше)?

     public partial class Array: Array { 

Обновить:

Второй момент также важен для предположения о том, что больше не требуется, чтобы не-общий Array был основным типом.

    история

    Какие проблемы возникнут, если массивы станут универсальными?

    Вернувшись в C # 1.0, они скопировали концепцию массивов в основном из Java. В то время дженерики не существовали, но создатели считали, что они умны и скопировали разбитую семантику семантических ковариантов, которую имеют массивы Java. Это означает, что вы можете отключить такие вещи без ошибки времени компиляции (но вместо этого – ошибки времени выполнения):

     Mammoth[] mammoths = new Mammoth[10]; Animal[] animals = mammoths; // Covariant conversion animals[1] = new Giraffe(); // Run-time exception 

    В C # 2.0 были введены дженерики, но не были ковариантными / контравариантными родовыми типами. Если массивы были сделаны родовыми, то вы не могли бросить Mammoth[] Animal[] , что-то, что вы могли бы сделать до этого (даже если оно было сломано). Таким образом, создание массивов generic сильно нарушило бы код.

    Только в C # 4.0 были ковариантными / контравариантными типичными типами для введенных интерфейсов. Это позволило зафиксировать ковариантность разбитых массивов раз и навсегда. Но опять же, это сломало бы много существующего кода.

     Array mammoths = new Array(10); Array animals = mammoths; // Not allowed. IEnumerable animals = mammoths; // Covariant conversion 

    Массивы реализуют общие интерфейсы

    Почему массивы не реализуют общие интерфейсы IList , ICollection и IEnumerable ?

    Благодаря трюку во время выполнения каждый массив T[] реализует IEnumerable , ICollection и IList автоматически. 1 Из документации classа Array :

    В .NET Framework версии 2.0 class Array реализует интерфейсы IList , ICollection и IEnumerable . Реализации предоставляются массивам во время выполнения и поэтому не видны инструментам сборки документации. В результате общие интерфейсы не отображаются в синтаксисе объявления для classа Array.


    Можете ли вы использовать все элементы интерфейсов, реализованные с помощью массивов?

    Нет. Документация продолжается с этим замечанием:

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

    Это потому, что (например) ICollection имеет метод Add , но вы не можете ничего добавить к массиву. Это вызовет исключение. Это еще один пример ранней ошибки проектирования в .NET Framework, которая позволит вам получить исключения, которые вы выбрали во время выполнения:

     ICollection collection = new Mammoth[10]; // Cast to interface type collection.Add(new Mammoth()); // Run-time exception 

    И поскольку ICollection не является ковариантным (по понятным причинам), вы не можете этого сделать:

     ICollection mammoths = new Array(10); ICollection animals = mammoths; // Not allowed 

    Конечно, теперь существует ковариантный IReadOnlyCollection который также реализуется массивами под капотом 1 , но он содержит только Count поэтому он имеет ограниченное использование.


    Array базового classа

    Если бы массивы были родовыми, нам все равно понадобился бы не общий class Array ?

    В первые дни мы это сделали. Все массивы реализуют неосновные интерфейсы IList , ICollection и IEnumerable через свой Array базового classа. Это был единственный разумный способ предоставить всем массивам конкретные методы и интерфейсы и является основным использованием базового classа Array . Вы видите тот же выбор для перечислений: они являются типами значений, но наследуют членов от Enum ; и delegates, унаследованные от MulticastDelegate .

    Можно ли теперь удалить базовый class базового classа без возможности Array общих Array ?

    Да, методы и интерфейсы, разделяемые всеми массивами, могут быть определены в общем classе Array если он когда-либо появился. И тогда вы можете написать, например, Copy(T[] source, T[] destination) вместо Copy(Array source, Array destination) с дополнительным преимуществом безопасности определенного типа.

    Однако, с точки зрения объектно-ориентированного программирования, хорошо иметь общий несущий базовый class Array который можно использовать для ссылки на любой массив независимо от типа его элементов. Точно так же, как IEnumerable наследует от IEnumerable (который все еще используется в некоторых методах LINQ).

    Может ли базовый class Array получить из Array ?

    Нет, это создало бы циклическую зависимость: Array : Array : Array: Array : ... Кроме того, это означает, что вы можете хранить любой объект в массиве (в конце концов, все массивы в конечном итоге наследуются от типа Array ).


    Будущее

    Можно ли добавить новый массив массива Array не слишком сильно влияя на существующий код?

    Нет. Хотя синтаксис можно было бы приспособить, существующая ковариация массива не может быть использована.

    Массив – это особый тип в .NET. У него даже есть свои собственные инструкции на общем промежуточном языке. Если дизайнеры .NET и C # когда-либо решаются пойти по этой дороге, они могут сделать синтаксический сахар синтаксиса T[] для Array (точно так же, как T? – синтаксический сахар для Nullable ), и по-прежнему использовать специальные инструкции и поддержку, которые распределяют массивы смежно в памяти.

    Тем не менее, вы потеряете способность бросать массивы Mammoth[] в один из своих базовых типов Animal[] , подобно тому, как вы не можете использовать List для List . Но ковариантность массивов все равно нарушается, и есть лучшие альтернативы.

    Альтернативы ковариации массива?

    Все массивы реализуют IList . Если интерфейс IList был превращен в правильный ковариантный интерфейс, тогда вы можете использовать любой массив Array (или любой другой список) для IList . Однако для этого требуется, чтобы интерфейс IList был перезаписан, чтобы удалить все методы, которые могут изменить базовый массив:

     interface IList : ICollection { T this[int index] { get; } int IndexOf(object value); } interface ICollection : IEnumerable { int Count { get; } bool Contains(object value); } 

    (Обратите внимание, что типы параметров на входных позициях не могут быть T как это приведет к потере ковариации. Однако object достаточно хорош для Contains и IndexOf , который просто вернет false при передаче объекта неправильного типа. И коллекции, реализующие эти интерфейсы, могут предоставить свой собственный общий IndexOf(T value) и Contains(T value) .)

    Тогда вы можете сделать это:

     Array mammoths = new Array(10); IList animals = mammoths; // Covariant conversion 

    Существует даже небольшое улучшение производительности, поскольку время выполнения не должно проверять, совместимо ли присвоенное значение с реальным типом элементов массива при установке значения элемента массива.


    Мой удар по нему

    Я принял удар о том, как будет работать такой тип Array , если он будет реализован на C # и .NET, в сочетании с реальными ковариантными интерфейсами IList и ICollection описанными выше, и он работает довольно хорошо. Я также добавил инвариантные IMutableList и IMutableCollection чтобы предоставить методы мутации, отсутствующие в моих новых интерфейсах IList и ICollection .

    Я построил вокруг него простую библиотеку коллекции, и вы можете загрузить исходный код и скомпилировать двоичные файлы из BitBucket или установить пакет NuGet:

    M42.Collections – специализированные коллекции с большей функциональностью, функциями и простотой использования, чем встроенные classы .NET.


    1 ) Массив T[] в .Net 4.5 реализует через свой базовый class Array : ICloneable , IList , ICollection , IEnumerable , IStructuralComparable , IStructuralEquatable ; и молча через runtime: IList , ICollection , IEnumerable , IReadOnlyList и IReadOnlyCollection .

    Совместимость. Массив – это исторический тип, который восходит к тому времени, когда не было никаких дженериков.

    Сегодня было бы разумно иметь Array , затем Array , затем конкретный class;)

    [Обновление, новые идеи, он чувствовал, что что-то пропало до сих пор]

    Что касается более раннего ответа:

    • Массивы ковариантны, как и другие типы. Вы можете реализовать такие вещи, как «object [] foo = new string [5];» с ковариацией, так что это не причина.
    • Совместимость, вероятно, является причиной не пересмотра дизайна, но я утверждаю, что это тоже не правильный ответ.

    Однако другая причина, о которой я могу думать, состоит в том, что массив является «базовым типом» для линейного набора элементов в памяти. Я думал об использовании Array , в котором вы также можете задаться вопросом, почему T является объектом и почему этот «объект» существует даже? В этом сценарии T [] – это то, что я считаю другим синтаксисом для Array , который является ковариантным с Array. Поскольку типы действительно различаются, я рассматриваю два случая одинаковыми.

    Обратите внимание, что как основной объект, так и основной массив не являются требованиями к языку OO. C ++ – прекрасный пример этого. Предостережение об отсутствии базового типа для этих базовых конструкций не в состоянии работать с массивами или объектами с использованием отражения. Для объектов, которые вы использовали для создания предметов Foo, которые делают объект «естественным». На самом деле, отсутствие базового classа массива делает невозможным делать Foo, что не так часто используется, но не менее важно для парадигмы.

    Следовательно, наличие C # без базового типа массива, но с богатством типов времени выполнения (в частности, reflection) является невозможным.

    Так что больше деталей …

    Где используются массивы и почему они являются массивами

    Наличие базового типа для чего-то столь же фундаментального, как массив, используется для многих вещей и с полным основанием:

    • Простые массивы

    Да, мы уже знали, что люди используют T[] , так же, как они используют List . Оба реализуют общий набор интерфейсов, а именно: IList , ICollection , IEnumerable , IList , ICollection и IEnumerable .

    Вы можете легко создать массив, если знаете это. Мы все знаем, что это правда, и это не интересно, поэтому мы движемся дальше …

    • Создавайте коллекции.

    Если вы копаете в List, вы получите в конечном итоге массив, а точнее: массив T [].

    Так почему? Хотя вы могли бы использовать структуру указателя (LinkedList), это совсем не то же самое. Списки представляют собой непрерывные блоки памяти и получают их скорость, будучи непрерывным блоком памяти. В этом есть много причин, но просто поставьте: обработка непрерывной памяти – это самый быстрый способ обработки памяти – есть даже инструкции для этого в вашем процессоре, которые делают это быстрее.

    Тщательный читатель может указать на то, что для этого вам не нужен массив, а непрерывный блок элементов типа «Т», который ИЛ понимает и может обрабатывать. Другими словами, вы можете избавиться от типа массива здесь, если вы убедитесь, что есть другой тип, который IL может использовать для выполнения того же самого.

    Обратите внимание, что существуют значения и типы classов. Чтобы сохранить наилучшую производительность, вам необходимо сохранить их в своем блоке как таковом … но для сортировки это просто требование.

    • Сортировочный.

    Маршаллинг использует базовые типы, с которыми соглашаются все языки для общения. Этими базовыми типами являются такие вещи, как byte, int, float, pointer … и array. Наиболее важным является то, как массивы используются в C / C ++, что выглядит следующим образом:

     for (Foo *foo = beginArray; foo != endArray; ++foo) { // use *foo -> which is the element in the array of Foo } 

    В основном это устанавливает указатель в начале массива и увеличивает указатель (с байтами sizeof (Foo)) до тех пор, пока он не достигнет конца массива. Элемент извлекается в * foo – который получает элемент, на который указывает указатель «foo».

    Заметим еще раз, что существуют типы значений и ссылочные типы. Вы действительно не хотите, чтобы MyArray просто хранил все в штучной упаковке как объект. Внедрение MyArray просто стало намного сложнее.

    Некоторые осторожные читатели могут указать на то, что здесь вам действительно не нужен массив, что верно. Вам нужен непрерывный блок элементов с типом Foo – и если это тип значения, он должен быть сохранен в блоке в виде (байтового представления) типа значения.

    • Многомерные массивы

    Так что еще … Как насчет многомерности? По-видимому, правила не такие черные и белые, потому что у нас уже нет всех базовых classов:

     int[,] foo2 = new int[2, 3]; foreach (var type in foo2.GetType().GetInterfaces()) { Console.WriteLine("{0}", type.ToString()); } 

    Сильный тип просто вышел из windows, и в итоге вы ICollection коллекции типов IList , ICollection и IEnumerable . Эй, как мы должны получить размер? При использовании базового classа Array мы могли бы использовать это:

     Array array = foo2; Console.WriteLine("Length = {0},{1}", array.GetLength(0), array.GetLength(1)); 

    … но если мы посмотрим на альтернативы, такие как IList , то нет эквивалента. Как мы это решаем? Должен ввести IList здесь? Конечно, это неправильно, потому что основной тип – просто int . Как насчет IMultiDimentionalList ? Мы можем сделать это и заполнить его методами, которые в настоящее время находятся в массиве.

    • Массивы имеют фиксированный размер

    Вы заметили, что есть специальные вызовы для перераспределения массивов? Это имеет все, что связано с управлением памятью: массивы настолько низки, что они не понимают, что такое рост или сокращение. В C вы использовали бы «malloc» и «realloc» для этого, и вам действительно нужно реализовать свои собственные «malloc» и «realloc», чтобы понять, почему именно фиксированные размеры важны для всех вещей, которые вы прямо выделяете.

    Если вы посмотрите на это, есть только несколько вещей, которые выделяются в «фиксированных» размерах: массивы, все базовые типы значений, указатели и classы. По-видимому, мы обрабатываем массивы по-разному, точно так же, как мы обрабатываем основные типы по-разному.

    Замечание о типе безопасности

    Так зачем же им все эти интерфейсы «точки доступа» в первую очередь?

    Лучшей практикой во всех случаях является предоставление пользователям безопасной точки доступа типа. Это можно проиллюстрировать путем сравнения кода следующим образом:

     array.GetType().GetMethod("GetLength").Invoke(array, 0); // don't... 

    для кода следующим образом:

     ((Array)someArray).GetLength(0); // do! 

    Тип безопасности позволяет вам быть неаккуратным при программировании. Если он используется правильно, компилятор обнаружит ошибку, если вы ее сделали, а не обнаружите ее во время выполнения. Я не могу достаточно подчеркнуть, насколько это важно – в конце концов, ваш код может вообще не вызываться в тестовом случае, а компилятор всегда будет его оценивать!

    Объединяя все это

    Итак … давайте все вместе. Мы хотим:

    • Сильно типизированный блок данных
    • Он постоянно хранит свои данные
    • IL, чтобы убедиться, что мы можем использовать classные инструкции процессора, которые быстро истекают кровью
    • Общий интерфейс, который предоставляет всю функциональность
    • Тип безопасности
    • Многомерность
    • Мы хотим, чтобы типы значений сохранялись как типы значений
    • И та же структура сортировки, что и любой другой язык
    • И фиксированный размер, поскольку это облегчает распределение памяти

    Это довольно немного требований низкого уровня для любой коллекции … для этого требуется, чтобы память была организована определенным образом, а также преобразование в ИЛ / ЦП … Я бы сказал, что есть веская причина, что он считается базовым типом.

    Поэтому я хотел бы знать, почему это не так:

    Причина в том, что дженерики не присутствовали в первой версии C #.

    Но я не могу понять, в чем проблема.

    Проблема в том, что он разбил бы огромный объем кода, который использует class Array . C # не поддерживает множественное наследование, поэтому строки, подобные этому

     Array ary = Array.Copy(.....); int[] values = (int[])ary; 

    будет нарушена.

    Если бы MS делала C # и .NET снова и снова с нуля, тогда, вероятно, не возникло бы проблемы с тем, чтобы сделать Array универсальным classом, но это не реальность.

    Как все говорят: оригинальный Array является универсальным, потому что не было никаких дженериков, когда оно появилось в v1. Спекуляция ниже …

    Чтобы сделать «Массив» общим (что будет иметь смысл сейчас), вы можете либо

    1. сохранить существующий Array и добавить общую версию. Это хорошо, но большинство применений «Массив» связаны с его увеличением с течением времени, и это, скорее всего, причина того, что вместо этого была реализована более эффективная реализация той же концепции List . На данный момент добавление общей версии «последовательного списка элементов, которые не растут» не выглядит очень привлекательным.

    2. удалите не-общий Array и замените его общей конструкцией Array с тем же интерфейсом. Теперь вам нужно сделать скомпилированный код для более старых версий для работы с новым типом вместо существующего типа Array . Хотя было бы возможно (и, скорее всего, трудно) для каркасного кода поддерживать такую ​​миграцию, всегда есть много кода, написанного другими людьми.

      Поскольку Array очень простой тип, почти каждая часть существующего кода (который включает в себя настраиваемый код с reflectionм и сортировкой с помощью собственного кода и COM) использует его. В результате цена даже крошечной несовместимости между версиями (1.x -> 2.x .Net Framework) была бы очень высокой.

    Таким образом, тип Array должен оставаться навсегда. Теперь у нас есть List качестве общего эквивалента.

    В дополнение к другим проблемам, о которых говорили люди, попытка добавить общий Array создаст еще несколько трудностей:

    • Даже если бы современные ковариационные функции существовали с момента введения дженериков, их было бы недостаточно для массивов. Процедура, которая предназначена для сортировки Car[] , сможет сортировать Buick[] , даже если она должна скопировать элементы из массива в элементы типа Car а затем скопировать их обратно. Копирование элемента из типа Car обратно в Buick[] самом деле не является безопасным по типу, но оно полезно. Можно определить интерфейс одномерного массива ковариантного массива таким образом, чтобы сделать возможной сортировку (например, путем включения метода Swap (int firstIndex, int secondIndex)], но было бы сложно сделать что-то такое же гибкое, как и массивы.

    • В то время как тип Array может хорошо работать для T[] , в системе с общим типом не будет никаких средств для определения семейства, которое будет включать в себя T[] , T[,] , T[,,] , T[,,,] и т. д. для произвольного числа индексов.

    • В .net нет никаких средств для выражения представления о том, что два типа следует считать идентичными, так что переменная типа T1 может быть скопирована в один из типов T2 и наоборот, причем обе переменные содержат ссылки на один и тот же объект. Кто-то, использующий тип Array , вероятно, захочет передать экземпляры кода, который ожидает T[] , и принять экземпляры из кода, который использует T[] . Если массивы старого стиля не могут быть переданы и из кода, использующего новый стиль, то новые массивы будут скорее препятствием, чем функцией.

    Могут существовать способы сглаживания системы типов, чтобы допускать тип Array который вел себя так, как должен, но такой тип будет вести себя во многих отношениях, которые полностью отличаются от других типов общего типа, и поскольку уже существует тип, который реализует желаемое поведение (т. е. T[] ), неясно, какие выгоды получат от определения другого.

    Возможно, я что-то пропустил, но если экземпляр массива не был выбран или не использовался как ICollection, IEnumerable и т. Д., Тогда вы ничего не получите с массивом T.

    Массивы бывают быстрыми и уже безопасны по типу и не требуют накладных расходов в боксе / распаковке.

    Interesting Posts

    код времени в Matlab

    Разбор строк в Java с вкладкой «\ t» разделителя с использованием split

    Clearcase UCM – перекрестная доставка против доставки вверх?

    Почему findFirst () выбрасывает исключение NullPointerException, если первый найденный элемент равен null?

    Обновление для Windows 10 Anniversary завершается неудачно

    Java JDBC – Как подключиться к Oracle с помощью tnsnames.ora

    Как сделать режим родственных источников найти предок (или эквивалент) в UWP

    Отсутствие подсветки синтаксиса в GVIM для Windows с помощью _vimrc

    Как переместить загрузочный раздел Windows 7 с одного диска на другой?

    Почему проценты margin / padding в CSS всегда вычисляются по ширине?

    Как я могу динамически включать модули Perl без использования eval?

    Почему% b производит SIGFPE, когда b равно нулю?

    Как вернуть результаты Mongoose из метода поиска?

    Центрировать карту в d3 с учетом объекта geoJSON

    Используйте 32-разрядные библиотеки jni на 64-битном Android-устройстве

    Давайте будем гением компьютера.