Last active
October 20, 2021 00:14
-
-
Save JustinGrote/d3cc6baf626bb04bf6d965c3df8a6371 to your computer and use it in GitHub Desktop.
Create a C# 10 record struct in PowerShell - https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -version 7.2 | |
using namespace system.collections.generic | |
using namespace system.reflection | |
function record { | |
param ( | |
[Parameter(Mandatory)][String]$Name, | |
[Parameter(Mandatory)][hashtable]$Properties, | |
[switch]$ReadOnly | |
) | |
[text.stringbuilder]$recordDef = '' | |
$Properties.Values.foreach{ | |
$namespace = $_ -is [Type] ? $PSItem.Namespace : $PSItem.Gettype().NameSpace | |
[void]$recordDef.AppendLine("using $namespace;") | |
} | |
if ($ReadOnly) {$ReadOnlyString = ' readonly'} | |
[string]$recordDefTemplate = "public$ReadOnlyString record struct $Name ({0});" | |
$primitiveTypes = @( | |
[bool] | |
[byte] | |
[sbyte] | |
[char] | |
[decimal] | |
[double] | |
[float] | |
[int] | |
[uint] | |
[long] | |
[ulong] | |
[short] | |
[ushort] | |
) | |
[string]$propertyDef = $Properties.GetEnumerator().foreach{ | |
if ($_.value -is [Type]) { | |
'{0} {1}' -f $PSItem.Value.Name, $PSItem.Name | |
} elseif ($_.value -is [string]) { | |
'{0} {1} = "{2}"' -f 'string', $PSItem.Name, $PSItem.Value | |
} elseif ($_.value.gettype() -in $primitiveTypes) { | |
'{0} {1} = {2}' -f $_.value.gettype().Name, $PSItem.Name, $PSItem.Value | |
} else { | |
throw "{0}: {1} is a non-primitive type and is not supported to have a default value via this command. You may still define a non-default value by specifying '{0} = [{1}]' instead" -f $_.name, $_.value.gettype().Fullname | |
} | |
} -join ', ' | |
[void]$recordDef.AppendLine(($recordDefTemplate -f $propertyDef)) | |
Write-Debug "Adding Type $Name using type definition: `n$recordDef" | |
Add-Type -TypeDefinition $recordDef | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Simple Record definition | |
record forecast @{ | |
lowTemp = [string] | |
highTemp = [string] | |
conditions = [string] | |
} | |
#Creation Method 1 | |
$myForecast = [forecast]::new(36,45,'sunny') | |
#Creation Method 2 | |
[forecast]$myForecast2 = @{ | |
lowTemp = 36 | |
highTemp = 45 | |
conditions = 'cloudy' | |
} | |
#Creation Method 3 | |
$myForecast3 = [forecast]@{ | |
lowTemp = 88 | |
highTemp = 150 | |
conditions = 'ON FIRE' | |
} | |
#These have a fancy ToString already built in: | |
"$myForecast" | |
#Output: forecast { conditions = sunny, highTemp = 45, lowTemp = 35 } | |
#Lets try them as a function parameter | |
function Get-Weather ([forecast]$foreCast) { | |
"Forecast is {0}/{1} with {2} conditions" -f $foreCast.lowTemp, $foreCast.highTemp, $foreCast.conditions | |
} | |
#I can use a hashtable in place of the type and not get an error, it will cast safely | |
Get-Weather @{ | |
lowTemp = 30 | |
highTemp = 40 | |
conditions = 'sunny' | |
} | |
#Output: Forecast is 30/40 with sunny conditions | |
#I can use a completely different object and as long as the same properties are used, it will work | |
Get-Weather ([PSCustomObject]@{ | |
lowTemp = 30 | |
highTemp = 40 | |
conditions = 'sunny' | |
}) | |
#Output: Forecast is 30/40 with sunny conditions | |
#Different properties will fail however so this is still "safe" | |
Get-Weather ([PSCustomObject]@{ | |
lowTemp = 30 | |
highTemp = 40 | |
conditions = 'sunny' | |
wrongproperty = 'nope' | |
}) | |
#Error: Cannot process argument transformation on parameter 'foreCast'. Cannot convert value '@{lowTemp=30; highTemp=40; conditions=sunny; wrongproperty=nope}' to type 'forecast'. Error: 'Cannot convert the '@{lowTemp = 30; highTemp = 40; conditions = sunny; wrongproperty = nope }' value of type 'System.Management.Automation.PSCustomObject" to type "forecast"." | |
#Alternative Record definition which will detect the type of value defaults | |
record defaultForecast @{ | |
lowtemp = 35 | |
hightemp = 45 | |
conditions = 'sunny' | |
} | |
[string][defaultForecast]::new() | |
#Output: defaultForecast { conditions = sunny, highTemp = 45, lowTemp = 35 } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment