Подтвердить что ты не робот

Почему класс Array не раскрывает свой индекс?

что-то сказать для ответа:

  • Не беспокойтесь о дисперсии, в то время как рассматриваемый предмет Array, а не T[].

  • Аналогичный случай для многомерных массивов - [здесь]

То есть N-dims к линейному преобразованию всегда возможно. Поэтому этот вопрос особенно привлек мое внимание, поскольку он уже реализовал IList для линейного индексатора.


Вопрос:

В моем коде у меня есть следующее объявление:

public static Array ToArray<T>(this T source); 

Мой код знает, как сделать souce представляет массив (во время выполнения). И я пытаюсь разрешить входящему коду обращаться к его индектеру напрямую. Но без "как Илиста" это не может быть сделано. Для возврата object[] может потребоваться дополнительное преобразование/кастинг, что я мешаю сделать. Я могу сделать это:

public static IList ToArray<T>(this T source); 

Но я бы подумал, что метод с именем ToArray возвращает IList выглядит странно.

Таким образом, я смущен этим:

В объявлении Array есть

object IList.this[int index];

Итак, мы можем

Array a;
a=Array.CreateInstance(typeof(char), 1);
(a as IList)[0]='a';

Но мы не можем

a[0]='a';

кроме того, если он был объявлен как

public object this[int index]; 

Единственное различие, которое я вижу, заключается в том, что он требует явного использования индексатора через интерфейс IList, с помощью которого он был реализован, но почему? Есть ли преимущества? Или возникают проблемы?

4b9b3361

Ответ 1

Array не может иметь индекс, потому что он должен иметь возможность представлять массив с любым количеством измерений. У индексатора для двумерного массива есть другая подпись, чем для одномерного массива.

Если указатель был предоставлен и использован на Array, который представлял собой двумерный массив, что должно произойти?

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

Если вы знаете, что ваш метод ToArray всегда будет возвращать одномерный массив, тогда рассмотрите возможность использования:

public static T[] ToArray<T>(this T source); 

У этого будет указатель.

Если элементы в массиве не будут иметь тип T, тогда вы можете вернуть object[]:

public static object[] ToArray<T>(this T source); 

Ответ 2

a as IList - это (в основном) кастинг. Поэтому просто бросьте сначала:

char[] a = (char[])Array.CreateInstance(typeof(char), 1);
a[0] = 'a';

Изменить: причина в том, что интерфейс для Array просто не определяет индексатор. Он использует SetValue(Object, Int32) и Object GetValue(Int32). Обратите внимание на зловещий материал Object. Array не зависит от типа; он построен для самого низкого общего знаменателя: Object. Он мог бы так же легко определить индексатора, но на практике у вас все еще будет проблема un/boxing.

Ответ 3

Я думаю, что одна из причин, почему Array не реализует этот индексатор напрямую, потому что все конкретные типы массивов (например, char[]) происходят от Array.

Это означает, что такой код будет легальным:

char[] array = new char[10];
array[0] = new object();

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

char[] array = new char[10];
array.SetValue(new object(), 0);

Но SetValue() обычно не используется, поэтому это не большая проблема.

Ответ 4

Проблема с методами IList<T> в классе Array, включая его индекс, заключается в том, что их явные реализации добавлены к объектам Array класс во время выполнения:

Начиная с .NET Framework 2.0, класс Array реализует общие интерфейсы System.Collections.Generic.IList<T>, System.Collections.Generic.ICollection<T> и System.Collections.Generic.IEnumerable<T>. Реализации предоставляются массивам во время выполнения и поэтому не видны инструментам сборки документации. В результате общие интерфейсы не отображаются в синтаксисе объявления для класса Array, и нет никаких эталонных тем для членов интерфейса, которые доступны только путем литья массива в общий тип интерфейса (явные реализации интерфейса).

Когда классы явно реализуют интерфейсы, доступ к методам интерфейса требует приведения:

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

Проблема с реализацией "регулярного" (в отличие от "явного" ) интерфейса заключается в том, что класс Array не является общим: без параметра типа вы не можете писать

class Array : IList<T>

просто потому, что T - undefined. Среда не может удалять реализацию интерфейса в класс Array до тех пор, пока не станет известен тип параметра T, который может произойти только во время выполнения:

// The type of [T] is char
Array a = Array.CreateInstance(typeof(char), 1);
// The type of [T] is int
Array b = Array.CreateInstance(typeof(int), 1);
// The type of [T] is string
Array c = Array.CreateInstance(typeof(string), 1);

В то же время статический тип a, b и c остается неизменным - it System.Array. Однако во время выполнения a будет реализован IList<char>, b будет реализовывать IList<int> и c - IList<string>. Ни один из них не известен во время компиляции, не позволяет компилятору "видеть" индекс и другие методы IList<T>.

Кроме того, не все экземпляры Array реализуют IList<T> - только массивы с одним измерением do:

Array x = new int[5];
Console.WriteLine(x.GetType().GetInterface("IList`1") != null); // True
Array y = new int[5,5];
Console.WriteLine(y.GetType().GetInterface("IList`1") != null); // False

Все вышеперечисленное не позволяет компилятору получить доступ к методам IList<T>, включая индекс, без явного приведения.

Ответ 5

Короткий ответ:

System.Array - это базовый класс для ND-массивов (не только 1-D), поэтому, почему 1-D-индекс (объект this [i] {get; set;}) не может быть базовый элемент.

Длинный ответ:

Если вы скажете, создайте 2-мерный массив и попробуйте обратиться к нему IList indexer:

Array a;
a=Array.CreateInstance(typeof(char), 1,1);
(a as IList)[0]='a';

Вы получите не поддерживаемое исключение.

Хороший вопрос:

Зачем System.Array реализовать IList и IEnumerable, в то время как большая часть его реализации будет бросать NotSupportedException для не-1-D массива??

Еще одно интересное. Технически не в массивах есть класс-индексатор внутри по классическому смыслу. Классическим значением Indexer является свойство "Item" + get (+ set) method (s). Если вы углубитесь в размышления, вы увидите, что typeof (string []) не имеет свойства индексатора и имеет только 2 метода Получить и Установить. > - метод, объявленный в классе string [] (не в базовом классе, в отличие от Array.SetValue, Array.GetValue), и они используются для индексирования во время компиляции.

Ответ 6

Даже массив , если был все 1D, у вас все еще была бы проблема с ковариацией и контравариантностью:

Если базовый класс имел

public Object this[int index] { get; set; }

свойство indexer, то свойства индекса индекса конкретных типов

public TValue this[int index] { get; set; }

будет сталкиваться с таковым базового типа (поскольку параметр имеет значение setter, но возвращаемое значение не является).

Передача базового класса в базовый интерфейс или общий интерфейс, такой как IList или IList, решает это, поскольку неспецифический индексатор может быть реализован явно. То же самое происходит с

Add(Object value)

против.

Add(TValue value)

методы.

Многомерная проблема могла бы, теоретически, преодолеть путем определения преобразования между 1D-индексами и индексами nD (например, [n] = [n/length (0), n% length (0) ]), так как nD-матрицы сохраняются как один непрерывный буфер.

Ответ 7

Технически существует два типа массивов. Типы векторов или типы матриц. Время выполнения относится к типам векторов как Sz_Array, и они являются типом, который вы получаете при объявлении массива 1d *. Я понятия не имею, почему. Типы матриц представляют собой многомерные массивы. К сожалению, они оба наследуют от Array и других типов-посредников.

Почему класс Array не раскрывает его указатель напрямую?

Причина, по которой вы можете получить доступ только к индектеру для 1d-массивов, когда у вас есть это как T[], потому что индексщик для массивов 1d реализован во время выполнения через коды операций IL.

например.

static T GetFirst<T>(T[] a)
{
   return a[0]:
}

Переводит на следующий il::

L_0000: ldarg.0 
L_0001: ldc.i4.0 
L_0002: ldelem.any !!T
L_0007: ret 

Где в качестве следующего С#

private static T GetFirst<T>(IList<T> a)
{
    return a[0];
}

переводит этот IL.

L_0000: ldarg.0 
L_0001: ldc.i4.0 
L_0002: callvirt instance !0 [mscorlib]System.Collections.Generic.IList`1<!!T>::get_Item(int32)
L_0007: ret 

Итак, мы видим, что используется код операции ldelem.any, а другой - callvirt метод.

Среда выполнения внедряет IList<T>,IEnumerable<T> во время выполнения для массивов. Логика для них в MSIL находится в классе SZArrayHelper

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

T Get(Int32)
void Set(Int32, T)

Эти методы также генерируются для матричных типов массивов в зависимости от измерения и используются С# при вызове индексаторов.

Однако, если вы не указали, что ваш типизированный массив, вы не получаете индексатора. Поскольку Array не имеет метода индексатора. Коды операций не могут использоваться, потому что во время компиляции вам нужно знать, что рассматриваемый массив представляет собой векторный тип и тип элемента массива.

Но массив реализует IList! У меня есть указатель, я не могу его назвать?

Да, это так, но реализация для IList-методов - это явные реализации, поэтому их можно вызывать только в С# при приведении или при ограничении общим ограничением. Вероятно, потому что для любого не-векторного типа массива он вызывает исключение, не поддерживаемое при вызове любого из его методов. Поскольку его только условно поддерживаемые создатели времени выполнения, вероятно, хотят, чтобы вы бросали его на время, когда вы знаете, что это 1d-массив, но я не могу назвать этот тип прямо сейчас.

Почему массив реализует IList, если для любых многомерных массивов, реализация которых не поддерживается?

Вероятно, это ошибка, которая была с 1.0. Теперь они не могут исправить это, поскольку кто-то по какой-то причине может отличать многомерный массив от IList. В 2.0 они добавили некоторую магию времени выполнения, чтобы добавить реализацию к классам векторного типа во время выполнения, так что только 1d массивы реализуют IList<T> и IEnumerable<T>.

И мы можем интерполировать следующий вопрос:

Как я могу заставить индексаторов отображаться без кастинга? Измените подпись метода на что-то вроде:

public static T[] ToArray<T>(this T source)

Но вы можете сказать, что ваш ToArray не возвращает T [], он возвращает что-то еще, что мне делать? Если вы можете явно указать тип возврата, просто сделайте это. Если это ссылочный вид, вы всегда можете злоупотреблять ковариацией массива и изменять тип возврата на object[], но тогда вы находитесь во власти ArrayTypeMismatchException. Это не сработает, если вы вернете ценностный тип, поскольку этот лимит является незаконным. На этом этапе вы можете просто вернуть IList, но тогда вы боксируете элементы, и вы по-прежнему находитесь во власти ArrayTypeMismatchException. Массив является базовым классом для всех типов массивов по той причине, что у него есть вспомогательные методы, которые помогут вам получить доступ к контенту типа GetValue и SetValue, и вы заметите, что у них есть перегрузки, которые принимают массивы индексов, чтобы вы могли получить доступ к элементам в Nd, а также 1d массивов. например.

IList myArray = ToArray(myValues);
// myArray is actually a string array.
myArray[0]='a';
// blows up here as you can't explicitly cast a char to a string.

Так что, если вы не знаете явный тип. И каждый наследник Array, реализующий IList, даже если это не имеет особого смысла, представляет собой деталь реализации, которую они не могут изменить, поскольку она была там с 1.0.

  • Технически это не 100% правда. Вы можете создать массив матричного типа, который имеет только 1 размер. Это мерзость eldritch может быть создана в IL или вы можете создать тип с помощью typeof(int).MakeArrayType(1) Обратите внимание, как теперь у вас есть System.Int32[*] вместо System.Int32[]

Ответ 8

Из msdn:

Класс Array является базовым классом для языковых реализаций, который поддержка массивов. Однако можно получить только систему и компиляторы явно из класса Array. Пользователи должны использовать массив конструкции, предоставляемые языком.

Если он предоставит вам индекс, он противоречит первоначальному намерению класса Array. Вы должны использовать реализацию компилятора.

Опять из msdn:

Важно: Начиная с .NET Framework 2.0, класс Array реализует System.Collections.Generic.IList, System.Collections.Generic.ICollection и System.Collections.Generic.IEnumerable общие интерфейсы. реализации выполняются в массивы во время выполнения, и поэтому не видны инструментам сборки документации. В результате общий интерфейсы не отображаются в синтаксисе объявления для массива класса, и нет никаких эталонных тем для членов интерфейса, которые доступны только путем заливки массива в общий тип интерфейса (явные реализации интерфейса). Главное, что нужно знать когда вы применяете массив к одному из этих интерфейсов, это члены которые добавляют, вставляют или удаляют элементы throw NotSupportedException.

Я думаю, что это запоздалая мысль.

Ответ 9

С# Specs "12.1.1 Тип System.Array" говорит: "Обратите внимание, что System.Array не является массивом типа

Потому что это не тип массива.

И обратите внимание, что "6.1.6 Неявные ссылочные преобразования" говорит: "Из одномерного массива типа S [] в System.Collections.Generic.IList и его базовые интерфейсы при условии, что существует неявное преобразование идентичности или ссылки от S до T"

С#: http://www.microsoft.com/en-us/download/details.aspx?id=7029

О том, почему доступ индексатора настолько таинственен, проверьте этот другой пост SO: неявная vs явная реализация интерфейса

надеюсь, что это поможет.