Powershell: принуждать или приводить к типу, указанному в строковой переменной

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

$pxAccelerators = @{
   pxListObject = '[System.Collections.Generic.List[Object]]'
   pxListString = '[System.Collections.Generic.List[String]]'
   pxOrderedDictionary = '[System.Collections.Specialized.OrderedDictionary]'
}

Тогда я мог бы использовать что-то вроде этого

$typeAccelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
foreach ($key in $pxAccelerators.Keys) {
    $name = $key
    $type = $pxAccelerators.$key
    $typeAccelerators::Add($key,$type)
}

чтобы перебрать хеш-таблицу и добавить каждый из них. Однако проблема, конечно, в том, что $type не является фактическим типом, это строка. И $typeAccelerators::Add($key,$type) нужна строка и фактический тип. Итак, в основном мне нужно привести строку типа '[System.Collections.Specialized.OrderedDictionary]' к фактическому типу. Я нашел множество ссылок на приведение или принуждение одного типа данных к другому, но я не могу найти ссылки на то, как преобразовать строку в тип, определенный строкой. Я пробовал все эти удары в темноте

([System.Type]'[System.Collections.ArrayList]')::new()
[System.Type]'[System.Collections.ArrayList]'
[System.Type]'[System.Collections.ArrayList]' -as [System.Type]
'[System.Collections.ArrayList]' -as ([PowerShell].Assembly.GetType('[System.Collections.ArrayList]')

но безрезультатно. $type = ([PowerShell].Assembly.GetType('[System.Collections.ArrayList]')), похоже, работает, поскольку не генерирует исключения. Но $type.GetType() бросает You cannot call a method on a null-valued expression.. Интересно, что автодополнение с [PowerShell].Assembly.GetType('[System.Collections.ArrayList]'). показывает, что такие свойства, как BaseType и FullName, доступны, предполагая, что я действительно создал тип, но использование .GetType() в результате вызывает исключение. Я попытался

$pxAccelerators = @{
   pxListObject = 'System.Collections.Generic.List[Object]'
   pxListString = 'System.Collections.Generic.List[String]'
   pxOrderedDictionary = 'System.Collections.Specialized.OrderedDictionary'
}
$typeAccelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
foreach ($key in $pxAccelerators.Keys) {
    $name = $key
    $type = [PowerShell].Assembly.GetType($pxAccelerators.$key)
    $typeAccelerators::Add($key,$type)
}

[PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Get

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

Наконец, я нашел этот который, кажется, приближается. Но я не могу понять, как получить доступ к методу, не начиная уже с какого-то типа, и [System.Type].GetType('System.Int32') выбрасывает, так что это кажется тупиком.

Я пытаюсь сделать что-то невозможное? Или просто отсутствует правильный механизм?


person Gordon    schedule 01.12.2020    source источник
comment
Имя типа не включает [], это просто способ написания литералов типа в PowerShell. Type.GetType выбрасывает, потому что это не метод экземпляра; [Type]::GetType('System.Int32') работает нормально. Даже с этим List[String] не будет работать, потому что это не то, как тип называется в .NET (это будет List`1, а затем создано с String); самый дешевый способ сделать это - Invoke-Expression '[System.Collections.Generic.List[String]]', предполагая, что вы можете быть уверены, что ваши строки никогда не будут получены из пользовательского ввода (поскольку iex выполняет произвольный код).   -  person Jeroen Mostert    schedule 01.12.2020
comment
Ага! Я даже не думал о invoke-Expression, но это потому, что я знаю, что это обычно небезопасно и его лучше избегать. Но здесь значения жестко закодированы, так что, если кто-то не изменит мой скрипт, это должно быть безопасно.   -  person Gordon    schedule 01.12.2020
comment
Все это говорит о том, почему бы просто не использовать [System.Collections.Generic.List[Object]].ToString()? Некоторые эксперименты показывают, что [Type]::GetType() будет правильно анализировать результирующую строку обратно в тип, то есть [Type]::GetType([System.Collections.Generic.List[Object]].ToString()) -eq [System.Collections.Generic.List[Object]] это true (но я проверяю это с PS 7.1, так что это может быть недавним нововведением - я не думаю, что раньше он обрабатывал дженерики правильно).   -  person Jeroen Mostert    schedule 01.12.2020


Ответы (2)


-as [type] сделаю

Оператор -as с удовольствием принимает имя типа в качестве правого операнда.

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

$pxAccelerators = @{
   pxListObject = 'System.Collections.Generic.List[Object]'
   pxListString = 'System.Collections.Generic.List[String]'
   pxOrderedDictionary = 'System.Collections.Specialized.OrderedDictionary'
}

$typeAccelerators = [PowerShell].Assembly.GetType("System.Management.Automation.TypeAccelerators")
foreach ($acc in $pxAccelerators.GetEnumerator()) {
    $name = $acc.Key
    $type = $acc.Value -as [type]
    $typeAccelerators::Add($name,$type)
}

Результат:

PS ~> [pxOrderedDictionary] -is [type]
True
PS ~> [pxOrderedDictionary].FullName
System.Collections.Specialized.OrderedDictionary
person Mathias R. Jessen    schedule 01.12.2020
comment
Интересно, что '[System.Collections.ArrayList]' -as [Type] не сработало, но и не выдало никакой ошибки. Потеряйте скобки в строке, и это сработает, но без ошибки, которую сложно понять. - person Gordon; 01.12.2020
comment
-as никогда не бросает по замыслу. [type]'System.Collections.ArrayList' или 'System.Collections.ArrayList' -as [type] сработало бы - person Mathias R. Jessen; 01.12.2020

В конце концов, ваша единственная проблема заключалась в том, что вы случайно заключили имена своих типов в [...]:

  • [...] – это специальная нотация PowerShell для типов литералов (например, [System.DateTime]); Методы .NET не распознают это, хотя строка внутри [...] (например, System.DateTime) использует надмножество независимой от языка нотации, распознаваемой Методы .NET, такие как System.Type.GetType() — см. нижний раздел. .

В вашем случае вам даже не нужно явное преобразование строк имени вашего типа в объекты [type], поскольку PowerShell неявно выполняет это преобразование, когда вы вызываете метод ::Add(). Поэтому достаточно следующего:

@{
   pxListObject = 'System.Collections.Generic.List[Object]'
   pxListString = 'System.Collections.Generic.List[String]'
   pxOrderedDictionary = 'System.Collections.Specialized.OrderedDictionary'
}.GetEnumerator() | ForEach-Object {

  # Calling ::Add() implicitly converts the type-name *string* to the required
  # [type] instance. 
  [powershell].Assembly.GetType('System.Management.Automation.TypeAccelerators')::Add(
    $_.Key,
    $_.Value
  )

}

Конечно, вы могли бы напрямую использовать тип литералы в качестве значений хеш-таблицы:

# Note the use of [...] values (type literals) instead of strings.
@{
   pxListObject = [System.Collections.Generic.List[Object]]
   pxListString = [System.Collections.Generic.List[String]]
   pxOrderedDictionary = [System.Collections.Specialized.OrderedDictionary]
}.GetEnumerator() | ForEach-Object {
  [powershell].Assembly.GetType('System.Management.Automation.TypeAccelerators')::Add(
    $_.Key,
    $_.Value
  )

}

Предупреждение:

  • Тип System.Management.Automation.TypeAccelerators является непубличным (поэтому необходимо получить к нему доступ через рефлексию), и полагаться на непубличные API проблематично.

  • Альтернативой, доступной в PowerShell v5+, является использование using namespace, поэтому вам не нужно указывать полное имя типа (пример см. все типы в указанном пространстве имен доступны по простому имени (без части пространства имен).


Что касается что вы пробовали:

$type = [PowerShell].Assembly.GetType('[System.Collections.ArrayList]'), похоже, работает, поскольку не генерирует исключения.

  • Как уже говорилось, внешние [...] не являются частью имени типа; однако здесь их удаления недостаточно, потому что указанный тип не является частью той же сборки, что и [PowerShell].

  • Как правило, System.Type.GetType() спокойно возвращает $null, если указанный тип не может быть найден; например: [PowerShell].Assembly.GetType('DefinitelyNoSuchType')

[PowerShell].Assembly.GetType($pxAccelerators.$key) предполагает, что строка GetType() на самом деле не создает тип.

Причина в том, что System.Type.GetType() не распознает закрытые универсальные типы, такие как System.Collections.Generic.List[String], поскольку базовая нотация поддерживает только открытые (такие как System.Collections.Generic.List`1); предыдущая нотация является специфическим для PowerShell расширением нотации — см. нижний раздел.


Преобразование имен типов, хранящихся в строкахбез закрывающего [...] — в System.Type ([type]) объекты:

Трансляция на [type]:

# Same as: [System.Collections.Generic.List[string]]; case does not matter.
[type] 'System.Collections.Generic.List[string]'
  • Приведение к [type] делает больше, чем вызов System.Type.GetType(), потому что строки имени типа PowerShell поддерживают надмножество нотации последнего — см. нижний раздел.

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

  • В качестве альтернативы вы можете использовать 'System.Collections.Generic.List[string]' -as [type], как показано в ответе Матиаса Р. Джессена, который спокойно возвращает $null, если преобразование в тип не невозможно; см. -as, оператор условного преобразования типов.

Обратите внимание, что и -as, и -is также принимают строки имени типа в качестве правой части; например 42 -is 'int' или '42' -as 'int'

Кроме того, командлет New-Object принимает строки в качестве аргумента -TypeName, поэтому опять же литерал, заключающий в себя [...], не должен использоваться; например,
New-Object -TypeName System.Text.UTF8Encoding


Обозначение литерала типа и имени типа в PowerShell:

  • PowerShell тип литерал (например, [System.DateTime]) — это тип имя, заключенное в [...], но [ и ] не являются частью имени.

    • Type literals are themselves instance of System.Type ([type]), or, more accurately, instances of the non-public [System.RuntimeType] type, which derives from System.Reflection.TypeInfo, which in turn derives from System.Type.
  • строки с именами типов обычно также можно использовать там, где допустимы литералы типов (объекты, представляющие типы), при этом PowerShell преобразует в [type] неявно , особенно во время привязки параметров и в правой части -as, оператор условного преобразования типов и -is, оператор проверки типа (-наследование)/интерфейса.

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

  • Независимая от языка нотация .NET, используемая System.Type.GetType(), например, задокументирован в Указание полных имен типов и, в частности, требует и включает:

    • Имена типов должны быть (по крайней мере) имениями с указанием пространства имен (например, System.Int32, а не просто Int32) и должны быть указаны с учетом регистра точно по умолчанию; однако доступна перегрузка с параметром ignoreCase; имена могут быть опционально с указанием сборки, чтобы также указать исходную сборку типа.

    • Суффикс `<num> указывает арность универсальных типов, т. е. количество (<num>) аргументов типа, требуемых универсальным типом (например, `1 для типа с одним параметром универсального типа, например System.Collections.Generic.List`1).

      • Использование только арности универсального типа возвращает универсальное определение типа.

        • A generic type definition cannot be directly instantiated; instead, you must call the .MakeGenericType() instance method on the type object returned and pass (closed) type arguments for all generic type parameters, which then returns a closed [constructed] generic type, i.e. a concrete instantiation of the generic type definition from which instances can be constructed.
    • Если вы необязательно следуете за спецификатором универсального типа со списком аргументов типа, чтобы связать все параметры типа, определенные для универсального типа, сконструированный общий тип возвращается напрямую; если все аргументы типа закрыты сами по себе, транзитивно, т. е. не имеют несвязанных параметров типа, если применимо, возвращаемый тип является закрытым [сконструированным] типом, который может быть непосредственно создан (его конструкторы могут быть вызваны) . Список аргументов типа имеет форму [<type>] или, для нескольких аргументов типа, [<type>, ...]; Ссылки <type> регулируются теми же правилами, что и окружающие имена универсального типа, и могут дополнительно заключаться в [...] отдельно.

      • For instance, the following two type literals, which construct a closed Dictionary<TKey,TValue> type (to use C# notation for a change) with [string] keys and [int] values, are therefore equivalent:
        • System.Collections.Generic.Dictionary`2[System.String, System.Int32]
        • System.Collections.Generic.Dictionary`2[[System.String], [System.Int32]]
    • Суффиксы []/[,] указывают на одномерный/двумерный массив; например, System.Int32[].

    • + используется для отделения вложенных типов от содержащего их класса; например, System.Environment+SpecialFolder.

  • Ниже перечислены специфические для PowerShell расширения:

    • Сопоставление имен неизменно и нечувствительно к регистру; например, тип System.Text.UTF8Encoding может быть указан как [system.text.utf8encoding].

    • Вы можете опустить компонент System. в полном имени; например, [Text.UTF8Encoding]

    • Вы можете использовать имена PowerShell ускорители ввода; например, [regex].

    • В PowerShell v5+ вы также можете использовать using namespace оператор, поэтому вам не нужно указывать полное имя типа:

      • E.g., placing using namespace System.Text at the top of a script or script module then allows you to refer to [System.Text.UTF8Encoding] as just [UTF8Encoding]
    • При передаче списка аргументов универсального типа вы можете опустить спецификатор арности типа.

      • For instance, the following two type literals, which construct a closed Dictionary<TKey,TValue> type with [string] keys and [int] values, are therefore equivalent:
        • [System.Collections.Generic.Dictionary[string, int]]
        • [System.Collections.Generic.Dictionary`2[string, int]]
person mklement0    schedule 01.12.2020