Skip to content

Instantly share code, notes, and snippets.

@IISResetMe
Created November 29, 2021 23:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IISResetMe/9e3309c06c2fdec97581ce3cf9e5ee99 to your computer and use it in GitHub Desktop.
Save IISResetMe/9e3309c06c2fdec97581ce3cf9e5ee99 to your computer and use it in GitHub Desktop.
using namespace System.Reflection
using namespace System.Reflection.Emit
using namespace System.Runtime.CompilerServices
# We'll attempt to construct a subset of the functionality of:
# public record TestRecord(int M1, string M2);
#
# Namely, we'll generate:
# - property getters (`get_M1()`, `get_M2()`),
# - a public constructor (`TestRecord(int, int);`),
# - a copy-constructor (`TestRecord(TestRecord);`) and the accompanying `<Clone>$()` method
#
# NOT included in this sample:
# - Equality operators
# - IEquatable<TestRecord> implementations
# - ToString()/PrintMembers()
#
# define record signature
$typeName = 'TestRecord'
$members = [ordered]@{
M1 = [int]
M2 = [string]
}
# create assembly + dynamic module, this is where our record type will live
$assemblyName = [AssemblyName]::new("pwsh_assembly_$((New-Guid)-replace'\W')")
$assemblyAccess = [AssemblyBuilderAccess]::Run
$assemblyBuilder = [AssemblyBuilder]::DefineDynamicAssembly($assemblyName, $assemblyAccess)
$moduleBuilder = $assemblyBuilder.DefineDynamicModule("TestModule_$(New-Guid)")
# prepare attribute sets for later
$propertyMethodAttributes = [MethodAttributes]'Public,HideBySig,SpecialName'
$ctorAttributes = [MethodAttributes]'Public,HideBySig,SpecialName,RTSpecialName'
$typeAttributes = [TypeAttributes]'Public,AnsiClass,AutoLayout,BeforeFieldInit'
# define type builder and define members (backingfield + property + getter)
$typeBuilder = $moduleBuilder.DefineType($typeName, $typeAttributes, [System.Object])
$properties = [ordered]@{}
foreach ($member in $members.GetEnumerator()) {
$propertyName,$propertyType = $member.Name,$member.Value
# define backing field
$backingField = $typeBuilder.DefineField("<${propertyName}>k__BackingField", $propertyType, [FieldAttributes]'Private,InitOnly')
# define property
$property = $typeBuilder.DefineProperty($propertyName, [PropertyAttributes]'None', $propertyType, [type]::EmptyTypes)
# define property methods
$getter = $typeBuilder.DefineMethod(
"get_${propertyName}", # name
$propertyMethodAttributes, # attributes
[CallingConventions]::HasThis, # callingConvention
$propertyType, # returnType
[type]::EmptyTypes) # parameterTypes
$setter = $typeBuilder.DefineMethod(
"set_${propertyName}", # name
$propertyMethodAttributes, # attributes
[CallingConventions]::HasThis, # callingConvention
[void], # returnType
@([IsExternalInit]), # returnTypeRequiredCustomModifiers
$null, # returnTypeOptionalCustomModifiers
@($propertyType), # parameterTypes
$null, # parameterTypeRequiredCustomModifiers
$null) # parameterTypeOptionalCustomModifiers
# generate getter method IL, pretty straightforward:
# load backing field onto stack and return
$getterILGenerator = $getter.GetILGenerator()
$getterILGenerator.Emit([OpCodes]::Ldarg_0)
$getterILGenerator.Emit([OpCodes]::Ldfld, $backingField)
$getterILGenerator.Emit([OpCodes]::Ret)
$property.SetGetMethod($getter)
# generate setter method IL, also quite straightforward:
# load parameter argument onto stack and store it in the backing field
$setterILGenerator = $setter.GetILGenerator()
$setterILGenerator.Emit([OpCodes]::Ldarg_0)
$setterILGenerator.Emit([OpCodes]::Ldarg_1)
$setterILGenerator.Emit([OpCodes]::Stfld, $backingField)
$setterILGenerator.Emit([OpCodes]::Ret)
$property.SetSetMethod($setter)
# output property + field for later reference
$properties[$propertyName] = [pscustomobject]@{
Property = $property
BackingField = $backingField
}
}
# Now that we have variable references to all the relevant "building
# blocks" (stored in `$properties`), we'll create the public constructor
$baseCtor = [object].GetConstructor([type]::EmptyTypes)
$publicCtor = $typeBuilder.DefineConstructor($ctorAttributes, [CallingConventions]::HasThis, $members.get_Values())
$publicCtorILGenerator = $publicCtor.GetILGenerator()
$paramIndex = 1
foreach($memberName in $members.get_Keys()){
# this is entirely cosmetic - name the ctor parameters for the member names
$null = $publicCtor.DefineParameter($paramIndex, [ParameterAttributes]::None, $memberName)
# now generate the IL for loading this parameters argument onto the
# stack and then storing it in the corresponding backing fields
$publicCtorILGenerator.Emit([OpCodes]::Ldarg_0)
if ($paramIndex -lt 5) {
$publicCtorILGenerator.Emit([OpCodes]::"Ldarg_${paramIndex}")
}
else {
$publicCtorILGenerator.Emit([OpCodes]::Ldarg_S, [byte]$paramIndex)
}
$publicCtorILGenerator.Emit([OpCodes]::Stfld, $properties[$memberName].BackingField)
$paramIndex++
}
# call base ctor
$publicCtorILGenerator.Emit([OpCodes]::Ldarg_0)
$publicCtorILGenerator.Emit([OpCodes]::Call, $baseCtor)
$publicCtorILGenerator.Emit([OpCodes]::Nop)
$publicCtorILGenerator.Emit([OpCodes]::Ret)
# Create copy-constructor
$copyCtor = $typeBuilder.DefineConstructor($ctorAttributes, [CallingConventions]::HasThis, @($typeBuilder))
$copyCtorILGenerator = $copyCtor.GetILGenerator()
$null = $copyCtor.DefineParameter(1, [ParameterAttributes]::None, 'original')
# call base ctor
$copyCtorILGenerator.Emit([OpCodes]::Ldarg_0)
$copyCtorILGenerator.Emit([OpCodes]::Call, $baseCtor)
$copyCtorILGenerator.Emit([OpCodes]::Nop)
# copy member values one by one
foreach ($property in $properties.get_Values()) {
$copyCtorILGenerator.Emit([OpCodes]::Ldarg_0)
$copyCtorILGenerator.Emit([OpCodes]::Ldarg_1)
$copyCtorILGenerator.Emit([OpCodes]::Ldfld, $property.BackingField)
$copyCtorILGenerator.Emit([OpCodes]::Stfld, $property.BackingField)
}
$copyCtorILGenerator.Emit([OpCodes]::Ret)
# output new type
$typeBuilder.CreateType()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment