Типы Массивов В Powershell-Системе.Object[] против массивов с определенными типами

почему caling GetType().Name на массиве строк return Object[], а не String[]? Кажется, это происходит с любым типом элемента, например Import-Csv вы Object[] но каждый элемент PSCustomObject.

вот пример с массивом String

$x = @('a','b','c')

$x[0].GetType().Name #String
$x.GetType().Name #Object[]

2 ответов


потому что вы явно не указали тип данных массива.

например, присвоение целого числа $x[1] будет работать, потому что тип массива Object[].

Если вы укажете тип данных при построении массива, вы не сможете назначить значения несовместимого типа позже:

C:\PS> [int[]] $myArray = 12,64,8,64,12

C:\PS> $myArray.GetType()

IsPublic IsSerial Name                                     BaseType                   
-------- -------- ----                                     --------                   
True     True     Int32[]                                  System.Array               



C:\PS> $myArray[0] = "asd"
Cannot convert value "asd" to type "System.Int32". Error: "Input string was not in a c
orrect format."
At line:1 char:1
+ $myArray[0] = "asd"
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvalidCastFromStringToInteger

кончик шляпы к PetSerAl за всю его помощь.

дополнить полезный ответ Мирослава Адамца с почему PowerShell создает System.Object[] массивы по умолчанию и дополнительная справочная информация:

массивы PowerShell по умолчанию должны быть гибкий:

  • они позволяют объекты-магазине любой тип (включая $ null),
  • даже позволяет mix объекты разных типов в одном массиве.

чтобы включить это, массив должен быть (неявно) набран как [object[]] ([System.Object[]]), потому что System.Object является единственным корнем всей иерархии типов .NET, из которой происходят все остальные типы.

например, следующее создает [object[]] массив, элементами которого типа [string], [int], [datetime] и $null, соответственно.

$arr = 'hi', 42, (Get-Date), $null  # @(...) is not needed; `, <val>` for a 1-elem. arr.

если вы:

  • создать массив с помощью массив строительство оператор, ,

  • принудительный вывод команды в массив с помощью массив подвыражения оператор, @(...)

  • сохранить в переменной the выход из команды что выдает a коллекция объектов 2 или больше элементов независимо от конкретного типа оригинальной коллекции, или работать на нем в контексте другой команды по заключив его в (...)

вы всегда получить System.Object[] массив - даже если все элементы случайно имеют тот же тип, что и в вашем примере.


Дополнительное Дальнейшее Чтение

PowerShell массивы по умолчанию удобны, но имеют свои недостатки:

  • они обеспечивают нет типа безопасности: если вы хотите убедиться, что все элементы имеют определенный тип (или должны быть преобразованы к нему, если возможно), массив по умолчанию не подойдет; например:

    $intArray = 1, 2      # An array of [int] values.
    $intArray[0] = 'one'  # !! Works, because a [System.Object[]] array can hold any type.
    
  • [System.Object[]] массивы неэффективно для типы значений например [int], потому что бокс и анбоксинг должно быть выполнено-хотя это может часто не иметь значения в реальном мире.

поскольку PowerShell предоставляет доступ к системе типов .NET, вы можете избежать недостатков, если вы создайте массив, который ограничен определенным типом интереса, используя cast или тип-ограниченная переменная:

[int[]] $intArray = 1, 2  # A type-constrained array of [int] variable.
$intArray[0] = 'one'      # BREAKS: 'one' can't be converted to an [int]

обратите внимание, что с помощью cast для создания массива -$intArray = [int[]] (1, 2) - тоже сработало бы, но только переменная с ограничением типа гарантирует, что вы не можете позже присвоить значение другого типа переменная (например, $intArray = 'one', 'two' будет неудача.)

синтаксическая ловушка с гипс: [int[]] 1, 2 тут не работа по назначению, потому что отливки имеют высокий приоритет операторов, поэтому выражение оценивается как ([int[]] 1), 2, который создает регулярные [object[]] массив,первый элемент вложенные [int[]] массив с одним элементом 1.
когда сомневаетесь, используйте @(...) вокруг вашего выбора элементы[1], что также требуется, если вы хотите убедиться, что выражение, которое может возвращать только один элемент всегда рассматривается как массив.


подводные камни

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

  • PowerShell автоматически принудить значение к целевому типу, который вы не всегда хотите и можете не заметить:

    [string[]] $a = 'one', 'two'
    $a[0] = 1    # [int] 1 is quietly coerced to [string]
    
    # The coercion happens even if you use a cast:
    [string[]] $a = 'one', 'two'
    $a[0] = [int] 1    # Quiet coercion to [string] still happens.
    

    примечание: что даже явный бросок -[int] 1 - причины Тихого принуждения могут быть или не быть сюрпризом для вас. Мое удивление было вызвано-неправильно-предполагая, что на языке автоматического принуждения, таком как приведения PowerShell, может быть способ обход принуждение-которое не правда.[2]

    учитывая, что любой тип может быть преобразован в строка, a [string[]] array-самый сложный случай.
    Вы do получить ошибку, если (автоматическое) принуждение не может быть выполнено, например, с
    [int[]] $arr = 1, 2; $arr[0] = 'one' # error

  • "добавление в" специально типизированный массив создает new массив типа [object[]]:

    PowerShell удобно позволяет "добавлять" массивы с помощью + оператор.
    На самом деле,a new массив создается за кулисами с добавленным дополнительным элементом(элементами), но это новый массив по умолчанию снова типа [object[]], независимо от типа входного массива:

    $intArray = [int[]] (1, 2)
    ($intArray + 4).GetType().Name # !! -> 'Object[]'
    $intArray += 3 # !! $intArray is now of type [object[]]
    
    # To avoid the problem...
    # ... use casting:
    ([int[]] ($intArray + 4)).GetType().Name # -> 'Int32[]'
    # ... or use a type-constrained variable:
    [int[]] $intArray = (1, 2) # a type-constrained variable
    $intArray += 3 # still of type [int[]], due to type constraint.
    
  • вывод в поток успеха преобразует любую коллекцию в [object[]]:

    любая коллекция с по крайней мере 2 элементы что команда или конвейер выводит (поток успеха)автоматически преобразуется в массив типа [object[]], который может быть неожиданным:

    # A specifically-typed array:
    # Note that whether or not `return` is used makes no difference.
    function foo { return [int[]] (1, 2) }
    # Important: foo inside (...) is a *command*, not an *expression*
    # and therefore a *pipeline* (of length 1)
    (foo).GetType().Name # !! -> 'Object[]'
    
    # A different collection type:
    function foo { return [System.Collections.ArrayList] (1, 2) }
    (foo).GetType().Name # !! -> 'Object[]'
    
    # Ditto with a multi-segment pipeline:
    ([System.Collections.ArrayList] (1, 2) | Write-Output).GetType().Name # !! -> 'Object[]'
    

    причина такого поведения в том, что PowerShell принципиально коллекция на основе: вывод любой команды отправляется по пунктам через трубопровод; обратите внимание, что даже один - это трубопровода (длины 1).

    то есть PowerShell всегда первый разворачивает коллекций, а затем, при необходимости, собирает их - for назначение переменная или промежуточный результат a внутри (...) и собрана коллекция всегда типа [object[]].

    PowerShell считает объект коллекцией, если его тип реализует IEnumerable интерфейс, за исключением если он также реализует IDictionary интерфейс.
    Это исключение означает, что хэш-таблицы PowerShell ([hashtable]) и упорядоченные хэш-таблицы (буквальный вариант PSv3+ с упорядоченными ключами,[ordered] @{...} типа [System.Collections.Specialized.OrderedDictionary]) отправляются через трубопровод в целом, и вместо перечисления их записей (пар ключ-значение) по отдельности, вы должны вызвать их .GetEnumerator() метод.

  • PowerShell по дизайну всегда разворачивает a один-элемент коллекции вывод, что один элемент:

    другими словами: когда выводится одноэлементная коллекция, PowerShell не возвращает массив, но единого массива элемент .

    # The examples use single-element array ,1 
    # constructed with the unary form of array-construction operator ","
    # (Alternatively, @( 1 ) could be used in this case.)
    
    # Function call:
    function foo { ,1 }
    (foo).GetType().Name # -> 'Int32'; single-element array was *unwrapped*
    
    # Pipeline:
    ( ,1 | Write-Output ).GetType().Name # -> 'Int32'
    
    # To force an expression into an array, use @(...):
    @( (,1) | Write-Output ).GetType().Name # -> 'Object[]' - result is array
    

    грубо говоря,цель массив подвыражения оператор @(...) is: Всегда рассматривайте вложенное значение как коллекция, даже если он содержит (или обычно распаковать в) только один элемент:
    Если это один значение, оберните его [object[]] массив с 1 элементом.
    Значения, которые уже являются коллекциями, остаются коллекциями, хотя они преобразован в new [object[]] массив, даже если само значение уже is массив:
    $a1 = 1, 2; $a2 = @( $a1 ); [object]::ReferenceEquals($a1, $a2)
    выходы $false, доказывая, что массивы $a1 и $a2 не то же самое.

    сравните это с:

    • просто (...), который тут не per se изменить тип значения - его цель-просто уточнить приоритет или принудительный новый контекст синтаксического анализа:

      • если заключенный конструкция выражение (что-то анализируется в режим выражение), тип не изменить, например, ([System.Collections.ArrayList] (1, 2)) -is [System.Collections.ArrayList] и ([int[]] (1,2)) -is [int[]] как вернуть $true - тип сохраняется.

      • если заключенный конструкция команда (одного или нескольких сегментов трубопровод), потом по умолчанию применяется поведение разворачивания, например:
        (&{ , 1 }) -is [int] возвращает $true (одноэлементный массив был развернут) и (& { [int[]] (1, 2) }) -is [object[]] (the [int[]] массив был собран в [object[]] array) оба возвращают $true, потому что использование оператора вызова & сделал прилагаемую конструкцию команда.

    • (регулярный) подвыражения оператора $(...), обычно используется в расширяемых строках, которые показывает поведение разворачивания по умолчанию: $(,1) -is [int] и $([System.Collections.ArrayList] (1, 2)) -is [object[]] как вернуть $true.

  • возвращение коллекции в целом из функции или скрипта:

    иногда вы можете захотеть вывести коллекцию в целом, т. е. вывести его как один элемент, сохраняя его первоначальный тип.

    как мы видели выше, вывод коллекции as-is заставляет PowerShell развернуть ее и в конечном итоге собрать ее в обычный [object[]] массив.

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

    # Wrap array list in regular array with leading ","
    function foo { , [System.Collections.ArrayList] (1, 2) }
    # The call to foo unwraps the outer array and assigns the original
    # array list to $arrayList.
    $arrayList = foo
    # Test
    $arrayList.GetType().Name # -> 'ArrayList'
    

    In PSv4+ используйте Write-Output -NoEnumerate:

    function foo { write-output -NoEnumerate ([System.Collections.ArrayList] (1, 2)) }
    $arrayList = foo
    $arrayList.GetType().Name # -> 'ArrayList'
    

[1] Обратите внимание, что используя @(...) создать массив литералы не необходимые, потому что оператор построения массива , только создает массивы.
В версиях, предшествующих PSv5.1, вы также платите (в большинстве случаев, вероятно, незначительный) штраф за производительность, потому что ,-построены массив внутри @() эффективно клонировать by @() - см. ответ шахты для деталей.
что сказал:@(...) преимущества:
* Вы можете использовать тот же синтаксис, независимо от того, содержит ли литерал массива один (@( 1 ) или несколько элементов (@( 1, 2 )). Сравните это с использованием ,: 1, 2 и , 1.
* Вам не нужно ,-разделите строки a многострочный @(...) утверждения (хотя обратите внимание, что каждая строка затем технически становится своим собственным утверждением).
* Нет ошибок приоритета оператора, потому что $(...) и @(...) имеют самый высокий приоритет.

[2] PetSerAl предоставляет этот расширенный фрагмент кода, чтобы показать ограниченные сценарии, в которых PowerShell тут уважение бросает:

# Define a simple type that implements an interface
# and a method that has 2 overloads.
Add-Type '
  public interface I { string M(); } 
  public class C : I {
           string I.M()       { return "I.M()"; } 
    public string M()         { return "C.M()"; } 
    public string M(int i)    { return "C.M(int)"; } 
    public string M(object o) { return "C.M(object)"; } 
  }
'
# Instantiate the type and use casts to distinguish between
# the type and its interface, and to target a specific overload.
$C = New-Object C
$C.M()        
([I]$C).M()       # cast is respected
$C.M(1)
$C.M([object]1)   # cast is respected