Tworzenie generyków w PS jest tak ciężkie i nie przyjemne w pisaniu, że ludzie nie używają ich. Postanowiłem coś z tym zrobić i napisałem funkcje, które tworzą obiekty generyczne:
function New-Generic
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$type,
[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[string[]]$typeParameter,
[Parameter(Mandatory=$false, Position=3)]
[array]$constructorParameters=$null
)
$genericType = [type]($type + ‘`’ + $typeParameter.Count)
[type[]] $tParameters = $typeParameter
$closedType = $genericType.MakeGenericType($tParameters)
Write-Verbose "Creating object $closedType with parameters $constructorParameters"
return ,([Activator]::CreateInstance($closedType, [Object[]]$constructorParameters))
}
Najwięcej czasu w napisaniu tej funkcji zajęło mi dodanie przecinka po słowie return. Ponad jeden dzień spędziłem w szukaniu dlaczego kastuje mi do zwyczajnego obiektu. Ten przecinek robi jakąś magię :)
Powyższa funkcja tworzy obiekt, który jest generyczny. Postanowiłem owrappować tą funkcję z najczęściej używanymi kolekcjami generycznymi:
function New-List
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string] $typeParameter
)
return ,( New-Generic -type 'System.Collections.Generic.List' -typeParameter $typeParameter )
}
function New-LinkedList
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string] $typeParameter
)
return ,( New-Generic -type 'System.Collections.Generic.LinkedList' -typeParameter $typeParameter )
}
function New-Queue
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string] $typeParameter
)
return ,( New-Generic -type 'System.Collections.Generic.Queue' -typeParameter $typeParameter )
}
function New-SortedSet
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string] $typeParameter
)
return ,( New-Generic -type 'System.Collections.Generic.SortedSet' -typeParameter $typeParameter )
}
function New-Stack
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string] $typeParameter
)
return ,( New-Generic -type 'System.Collections.Generic.Stack' -typeParameter $typeParameter )
}
function New-HashSet
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string] $typeParameter
)
return ,( New-Generic -type 'System.Collections.Generic.HashSet' -typeParameter $typeParameter )
}
function New-Dictionary
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$keyType,
[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[string]$valueType
)
return ,(New-Generic -type 'System.Collections.Generic.Dictionary' -typeParameter $keyType,$valueType)
}
function New-SortedDictionary
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$keyType,
[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[string]$valueType
)
return ,(New-Generic -type 'System.Collections.Generic.SortedDictionary' -typeParameter $keyType,$valueType)
}
function New-SortedList
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$keyType,
[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[string]$valueType
)
return ,(New-Generic -type 'System.Collections.Generic.SortedList' -typeParameter $keyType,$valueType)
}
function New-KeyValuePair
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$keyType,
[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[string]$valueType
)
return ,(New-Generic -type 'System.Collections.Generic.KeyValuePair' -typeParameter $keyType,$valueType)
}
Przykład użycia dla listy jest poniżej. Można zauważyć rzucenie wyjątku kiedy będziemy chcieli dodać obiekt do listy, który nie może być kastowany do int:
$list = New-List "int" -Verbose
$list.GetType()
$list.Add(1)
$list.Add('2')
$list.Add("three") #throw exception
Również można zainicjalizować stos doubli:
$stack= New-Stack -typeParameter 'double'
$stack.getType()
$stack.Push(1)
$stack.Push(20000)
$stack.Push(-1.123)
$stack.Pop()
A tworzenie słownika wygląda następująco:
$dic = New-Dictionary -keyType 'string' -valueType 'int'
$dic.Add("one",1)
$dic.Add("two",2)
Zanim stworzy się funkcję dla tupli to będzie potrzebna funkcja zwracająca domyślne wartości:
function Get-DefaultValue
{
param(
[Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
[type]$type
)
process{
if($type.IsValueType)
{
return [Activator]::CreateInstance($type)
}
return $null
}
}
Wynik dla boola:
Get-DefaultValue 'bool' #False
A funkcja do tworzenia tupli jest następująca:
function New-Tuple
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string[]] $typeParameter ,
[Parameter(Mandatory=$false, Position=1)]
[array]$constructorParameters
)
if(-not($constructorParameters))
{
$constructorParameters = $typeParameter |Get-DefaultValue
}
return ,( New-Generic -type 'System.Tuple' -typeParameter $typeParameter -constructorParameters $constructorParameters )
}
Oczywiście można stworzyć tuple dla dowolnej (o ile pozwoli konstruktor) ilości parametrów
$tuple3= new-Tuple -typeParameter 'int', 'object', 'string'
$tuple3.GetType();
$tuple3.Item1
$tuple3= new-Tuple -typeParameter 'int', 'bool', 'string' -constructorParameters 1,$true, "SuperString"
$tuple3.Item2
$tuple3.Item3