Интересные моменты, которые вы, возможно, не знали о C# (Array)

    В продолжении статьи «8 фактов, которые вы, возможно, не знали о C#» описывающей интересные моменты языка C#, представляю крохотный очерк. Очень жаль, что некоторые воспринимают такие статьи как “капитанство” отбивая всякое желание к написанию, но несмотря на это, в комментариях, порой, всплывает много полезной информации.

    Итак, что возвращают функции поиска индекса в массивах, если элемент не найден?


    Предположим у нас есть метод возвращающий Array:
    Array GetArray() { return … }
    


    И код который ищет в нем число:
    Array array = GetArray();
    int index = Array.IndexOf(array, 42);
    if (index != -1)
    {
    	// do something
    }
    


    Все знакомо и привычно, но сверившись с документацией, с удивлением обнаруживаем что методы IndexOf(Array ...), в случае не нахождения элемента возвращают не -1, а нижнюю границу массива - 1.

    Отсюда вытекает два вывода:
    1. Можно создать массив начинающийся не с 0.
    2. Код выше некорректен.

    Пример создания массива не с 0:
    Array array = Array.CreateInstance(typeof(int), new int[] { 3 }, new int[] { -1 });
    

    В этом примере создается массив из трех элементов, индексация начинается с -1.

    Правильный код выглядит следующим образом:
    Array array = GetArray();
    int index = Array.IndexOf(array, 42);
    if (index != (array.GetLowerBound(0) - 1))
    {
    	// do something
    }
    


    Я думаю эта особенность создана для языков платформы .NET где индексация начинается с 1.

    Помимо этого, в документации описан отдельный случай, когда нижняя граница равна int.MinValue. В этом случае метод IndexOf вернет int.MaxValue что соотвествует int.MinValue - 1 (переполнение).

    Всем спасибо за внимание!

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 35

      +9
      Немного поправлю — это не особенность C#, но особенность .NET. И весьма логичная.
        +3
        Не то чтобы я хочу поспорить, но интересно, почему вы считаете это логичнее, чем возвращать -1?
          +4
          Ответ, по-моему мнению, был дан в статье…

          1) языки программирования «считающие» начало «стандартных» массивов с 1 -> выдавать 0 при поиске очень удобно (как и с массивом C#)

          2) мы имеем возможность создать массив, нумеруя его, скажем с -3 до 5 -> -1 означает конкретный индекс, а не отсутствие рез-та.

          Да, пока писал, в голову пришла мысль, что де, можно было задать в indexOf смещение массива (0 — первый элемент, 1- второй) вне зав-ти от его индексации. Скажем, для приведенного во втором примере массива indexOf == 0 означал бы всего лишь -3 индекс. Но в этом случае опять бы возникла путаница и новые открытия, что де, платформа .NET не возвращает индекс, а возвращает смещение относительно начала. И снова интриги, расследования…
          • UFO just landed and posted this here
              +2
              Вероятно, самокрут имел ввиду Nullable в качестве результата для метода indexof. И таки да, я с ним согласен — Nullable тут удобнее ибо есть Null.
                0
                Использовать nullable, где будем проводить проверки на наличие значений и доставать их? Думаю, что это не очень изящное решение.
                В этом случае лучше воспользоваться идеей C# и назвать это «нештатной» ситуацией и выдавать Exception наподобие OutOfRange. Но опять же это усложнение совершенно простой с точки зрения решаемой задачи функции.
                  0
                  Дело в том, что Nullable — ссылочный тип.
                  Поэтому, если кто-то пишет оптимальный парсер больших данных, отказываясь загрязнения кучи вырезкой подстрок (Substring и т.п.), работает только с одной большой строкой и индексами на ней, ему придётся отказаться и от такого IndexOf с nullable, сделав свой IndexOf, возвращающий (-1) при неудаче.
                    +2
                    Nullable — это не ссылочный тип.
                      0
                      А, точно! Спасибо за поправку
                      +2
                      Подошло бы тогда и bool IndexOf(T value, out T index)
                        0
                        В принципе, да, как у IDictionary.TryGetValue.
                        С ним только такое неудобство, что громоздко использовать в linq-выражениях.
                        Поэтому приходится писать обёртку типа V GetValueByKey(K key, V defaultValueIfNotFound).
            +4
            Дополню топик ссылкой на исходный код этого метода: http://www.dotnetframework.org/, строка 846
            Наш метод
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
            public static int IndexOf(Array array, Object value) {
            	if (array==null)
            		throw new ArgumentNullException("array");
             	int lb = array.GetLowerBound(0);
            	return IndexOf(array, value, lb, array.Length);
            }
            

            Вызывает другой перегруженный метод, в конце которого мы видим (часть кода опустил):
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
            public static int IndexOf(Array array, Object value, int startIndex, int count) {
                       ...
                        // Return one less than the lower bound of the array.  This way,
                        // for arrays with a lower bound of -1 we will not return -1 when the
                        // item was not found.  And for SZArrays (the vast majority), -1 still
                        // works for them.
                        return lb-1;
            }
            
              +3
              Любопытно, что для generic-массивов нет такой особенности:

              public static int IndexOf(T[] array, T value)
              Member of System.Array

              Returns:
              The zero-based index of the first occurrence of value within the entire array, if found; otherwise, –1.
                +2
                И у методов IndexOf где первый параметр типизированный массив IndexOf(T[] ...)
                  +1
                  Это и есть generic. Прочитайте предыдущий комментарий внимательнее.
                    0
                    Нет, не дженерик, откройте Array класс, и у него есть методы принимающие T[] в IndexOf и там четко написано, что возвращается -1, а в методах принимающих Array первым параметром, то, что я описал в статье.
                      0
                      Похоже, я всех запутал.

                      Тип Int32[ ], который я ошибочно назвал generic-массивом, на самом деле не является Generic-типом (по мнению reflection, IsGenericType=false) и наследуется от System.Array. Сам по себе экземпляр типа Array не создаётся без указания типа элементов. Поэтому «не-generic массивов» (объектов типа Array) не бывает.
                  0
                  Дело в том, что T[] попадает под описание " type [ n ] " и соответственно тип такого массива — ELEMENT_TYPE_SZARRAY, что значит что это одномерный массив с индексацией от нуля.
                    0
                    Вообще автор откопал довольно странные массивы.
                    Они не совместимы с обычными System.Int32[] или System.Int32[,]
                    Их тип называет себя System.Int32[*]
                    Даже оператор индексации array[i]=a с ними не работает, нужно использовать array.SetValue(a, i);

                    Поэтому вряд ли кто-то столнётся с ними, ещё менее вероятно, что с IndexOf на них.
                      0
                      * в типе говорит о том, что CLR знает, что это не SZArray (для многомерных звёздочка не пишется, т.к. она была бы везде и не информативна).
                      Массивы, которые задаются как T[] — всегда индексируются с нуля, т.к. на этом основано куча оптимизаций (для SZArray в CLR есть отдельные команды, которые позволяют с такими массивами эффективнее работать, ибо нет смещения).

                      Если посмотреть например на сортировку массивов, то в Array.Sort реализованы методы, которые сортируют как T[], так и Array.
                        0
                        Array это все масивы [ ] во всем NET
                        Возмите тип у int[] и посмотрите какой у него базовый класс. Базовым классом будет аккурат Array.
                        Он не типизирован, поэтому у него нет индексаторов а доступ только через методы, причем всегда с боксингом для валю типов. Т.е. я откопал довольно не странный массив =)
                          0
                          Multidimensional Array System.Int32[*], создаваемый через Array.CreateInstance, странный, потому что для него нет поддержки в языке, сравнимой с обычными линейными, двумерными и N-мерными массивами (создание и инициализация через new, индексатор).

                          Array — это абстрактный класс, поэтому факт, что System.Int32[*] — потомок Array, ничего особенно не значит (поддержка контракта, не более)

                          То есть, не занимаясь интеграцией с чужими языками, вероятность встретить такой массив ничтожна, поэтому я его назвал странным.
                            0
                            У нас много интроинспекции в инструментарии, и со всеми масивами [ ] работа производится через Array.
                              0
                              А всё-таки, массивы типа T[*] живут в проекте? ;)
                                0
                                Если есть поле и у него тип это массив [ ] каких то типов заранее неизвестных, то через рефлексию создается как раз Array нужного типа, заполняется через методы и присваивается полю. Для этого Array и является базовым. чтобы можно было работать с масивами имея тип как Type.
                                  +1
                                  Если есть поле и у него тип это массив [ ]

                                  Значит, это обычный массив. Полю типа int[] нельзя присвоить массив System.Int32[*] — несовпадение типов возникнет.

                                  Другие перегруженные методы, например
                                  Array.CreateInstance(typeof(int), n1),
                                  Array.CreateInstance(typeof(int), n1, n2),
                                  Array.CreateInstance(typeof(int), new[]{n1, n2})
                                  (т.е. без указания массива LowerBounds), создают обычные массивы System.Int32[] и System.Int32[,]
                                    0
                                    Ясна, я понял о чем Вы.
                                      0
                                      Тогда да, получается что с таким типом можно столкнутся в редких случаях.
                            0
                            Конкретно для пример из статьи, метод может быть такой

                            Array GetArray() { return new int[] { }; }
                              0
                              А может и такой:
                              object GetArray() { return new int[] { }; }

                              это не значит, что так надо писать.
                      • UFO just landed and posted this here
                          0
                          Я предполагаю еще писать, под общим названием, и внутри обновлять ссылки. Извиняюсь, если ввел в заблуждение.
                            0
                            Ожидал, что здесь будет более одного интересного «момента»…
                          0
                          Очень жаль, что некоторые воспринимают такие статьи как “капитанство” отбивая всякое желание к написанию, но несмотря на это, в комментариях, порой, всплывает много полезной информации.


                          Участвуете в конкурсе на самую короткую статью? 50 строк вместе с кодом, круто. На stackoverflow вопросы длиннее.
                            +1
                            Посмотрел Вашу последнюю статью — 32 строки, аккурат как и у меня =)

                          Only users with full accounts can post comments. Log in, please.