Поскольку мне нужно иметь возможность добавлять и удалять элементы из массива, мне нужно преобразовать его как ArrayList
в отличие от более распространенных вариантов ([string[]]
, [Array]
, $Var=@()
). Мне также нужно предварительно установить его с данными, но вызывающему функции нужна возможность изменить предустановку по своему желанию. Предустановка в Param()
блок требуется, поскольку другой объект ищет предустановленные данные. Я пробовал несколько вариантов, но вот тот, который имеет наибольший смысл:
Function Test-Me{
Param
(
$Properties = [System.Collection.ArrayList]@(
"red",
"blue",
"green")
)
$Properties.GetType()
$Properties.Add("orange")
}
Вышеизложенное прекрасно, за исключением того, что как только кто-то позвонит Test-Me -Properties "Purple","Yellow","Black"
то $Properties
переменная становится стандартом Array
type (при этом методы добавления и удаления не работают).
Я пробовал методы изменения того, как я объявлял предустановленные значения. Похоже, что предварительное заполнение преобразует тип в обычный массив. Я подумал, что это потому, что я использовал @()
с предустановками, поэтому я пробовал ()
также.
Это тоже не работает:
Param
(
[System.Collection.ArrayList]
$Properties = @("red",
"blue",
"green")
)
У меня есть работа, которая преобразует тип вне блока Param, и это выглядит так:
Function Test-Me{
Param
(
[String[]]
$Properties = @("red",
"blue",
"green")
)
if("Orange" -notin $Properties){
[System.Collections.ArrayList]$Properties = $Properties
$Properties.Add("orange")
}
}
Я чувствую, что могу сыграть роль ArrayList
в блоке параметров и предустановить его данными, и вернуться с тем же типом данных, но я не мог этого понять. Если кто-то это сделает или найдет документацию, почему это не сработает, ответьте.
Второй опубликованный вами пример (явное приведение переменной параметра) является правильный путь:
Function Test-Me {
Param(
[System.Collections.ArrayList]
$Properties = @("red","blue","green")
)
Write-Host $Properties.GetType().FullName
if("Orange" -notin $Properties){
[void]$Properties.Add("orange")
}
$Properties
}
В результате чего:
PS C:\> Test-Me
System.Collections.ArrayList
red
blue
green
orange
PS C:\> Test-Me -Properties "one","two","three"
System.Collections.ArrayList
one
two
three
orange
Одна вещь, которая меня удивила, это то, что даже с [OutputType]
атрибут, функция выводит обычный массив (на самом деле это может быть Жук предполагаемое поведение, см. обновление ниже):
Function Test-Me {
[OutputType([System.Collections.ArrayList])]
Param(
[System.Collections.ArrayList]
$Properties = @("red","blue","green")
)
if("Orange" -notin $Properties){
[void]$Properties.Add("orange")
}
$Properties
}
По-прежнему возвращается обычный массив объектов:
PS C:\> (Test-Me).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Как показано в комментарии к вашему Подключить сообщение об ошибкеPowerShell намеренно перечисляет элементы любого перечисляемого вывода, чтобы обеспечить согласованное поведение конвейера (псевдо-C #, украденный у комментатора Дерп МакДерп на Connect):
if (returned_value is IEnumerable) {
foreach (r in returned_value) {
yield r;
}
} else {
yield returned_value;
}
Уловка состоит в том, чтобы обернуть коллекцию в массив с одним элементом, заставляя PowerShell передавать ее по конвейеру как отдельный элемент (примечание к ,
перед $Properties
):
Function Test-Me {
[OutputType([System.Collections.ArrayList])]
Param(
[System.Collections.ArrayList]
$Properties = @("red","blue","green")
)
if("Orange" -notin $Properties){
[void]$Properties.Add("orange")
}
,$Properties
}
и теперь мы получаем результат правильного типа:
PS C:\> (Test-Me).GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True ArrayList System.Object
Сотрудник просто просил меня объяснить некоторые детали, связанные с этим, поэтому я подумал, что может быть полезно поделиться некоторой дополнительной информацией с другими, кто придет сюда с этим вопросом в будущем.
В PowerShell есть три основных способа присвоения значений переменным:
# Loosely typed
# Can be reassigned to anything without error
$myVariable1 = @('Red','Green','Blue')
# Type cast
# Still loosely typed
# Can still be reassigned to anything without issue
$myVariable2 = [System.Collections.ArrayList]@('Red','Green','Blue')
# Strongly typed
# Can only be assigned objects of that type or objects that implicitly convert into that type
[System.Collections.ArrayList]$myVariable3 = 'Red','Green','Blue'
Если вы хотите убедиться, что переменная (или параметр функции) имеет определенный тип, вы должны использовать строгую типизацию, как это было сделано с $myVariable3
. Это предотвратит случайное изменение типа на что-то другое (например, $myVariable3 = 42
выйдет из строя с ошибкой).
Полезно знать, что вы можете определить, когда переменная строго типизирована, используя Get-Variable
командлет. Например, вызов Get-Variable myVariable1,myVariable2,myVariable3 | Format-List Name,Value,Visibility,Options,Attributes
дает следующий результат:
Name : myVariable1
Value : {Red, Green, Blue}
Visibility : Public
Options : None
Attributes : {}
Name : myVariable2
Value : {Red, Green, Blue}
Visibility : Public
Options : None
Attributes : {}
Name : myVariable3
Value : {Red, Green, Blue}
Visibility : Public
Options : None
Attributes : {System.Management.Automation.ArgumentTypeConverterAttribute}
Обратите внимание, что все они одинаковы, за исключением того, что myVariable3
переменная имеет атрибут в Attributes
свойство. An ArgumentTypeConverterAttribute
добавляется к любой строго типизированной переменной, и этот атрибут гарантирует, что переменная останется одного и того же типа независимо от того, чему вы ее назначите.
Понимание того, как работает присваивание переменных, а также как вернуть коллекцию без изменений, как указал Матиас выше, значительно упрощает работу с данными в PowerShell.