I bounced into this issue: #24898
: MemberwiseClone is missing after upgrade to powershell 7.5.0:
class MyClass {
[string]$Name
[object] CloneProblem() {
return $this.MemberwiseClone()
}
}
$obj = [MyClass]::new()
$obj.CloneProblem()
InvalidOperation: Line | 6 | return $this.MemberwiseClone() # Fails with "does not contai … | ~~~~~~~~~~~~~~~~~~~~~~~ | Method invocation failed because [MyClass] does not contain a method named 'MemberwiseClone'.
What would be the most concise and/or performant workaround to create a PowerShell shallow copy method alternative taking in consideration that the concerned class might have several derivatives MyClass1 : MyClass { }
meaning that I don't want the class type hardcoded in the concerned method.
The fastest is to create the new object hardcoding all existing members.
class MyClass {
[string] $Name
[object] CloneProblem() {
return [MyClass]@{ Name = $this.Name }
}
}
The most concise but less performant if the type has many members is to enumerate the properties accessing PSObject
member.
class MyClass {
[string] $Name
[object] CloneProblem() {
$clone = [ordered]@{}
foreach ($property in $this.PSObject.Properties) {
$clone[$property.Name] = $property.Value
}
return [MyClass] $clone
}
}
Alternatively, if you don't want to hardcode the type in the return
statement, you could use LanguagePrimitives.ConvertTo
:
return [System.Management.Automation.LanguagePrimitives]::ConvertTo(
$clone, $this.GetType())
Yet another less performant method is to invoke MemberwiseClone
via reflection, ideally the MethodInfo
should be cached in a static
field.
class MyClass {
[string] $Name
hidden static [System.Reflection.MethodInfo] $s_method
[object] CloneProblem() {
if (-not $this::s_method) {
$this::s_method = [object].GetMethod(
'MemberwiseClone',
[System.Reflection.BindingFlags] 'NonPublic, Instance')
}
return $this::s_method.Invoke($this, $null)
}
}
A follow-up on the previous approach, probably overkilling it as the previous approach should be sufficient in every possible case, can be storing Func<>
delegates stored in a static dictionary for the base and derived classes.
using namespace System.Collections.Generic
using namespace System.Linq.Expressions
using namespace System.Reflection
class BaseClass {
[int] $Age
hidden static [Dictionary[type, Delegate]] $s_cloneDelegates
[object] CloneProblem() {
if (-not $this::s_cloneDelegates) {
$this::s_cloneDelegates = [Dictionary[type, Delegate]]::new()
}
$type = $this.GetType()
if (-not $this::s_cloneDelegates.ContainsKey($type)) {
$this::s_cloneDelegates[$type] = [Delegate]::CreateDelegate(
[Expression]::GetFuncType($type, [object]),
[object].GetMethod(
'MemberwiseClone',
[BindingFlags] 'NonPublic, Instance'))
}
return $this::s_cloneDelegates[$type].Invoke($this)
}
}
class MyClass : BaseClass {
[string] $Name
}
$base = [BaseClass]::new()
$base.CloneProblem().GetType() # BaseClass
$derived = [MyClass]::new()
$derived.CloneProblem().GetType() # MyClass