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.
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.
2 Answers
Reset to default 2The 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
To complement Santiago's helpful answer, which shows several helpful techniques:
What would be the most concise and/or performant workaround?
The most concise - but not performant - solution is to use reflection on every invocation:
class MyClass {
[string]$Name
[object] CloneProblem() {
return [object].GetMethod(
'MemberwiseClone',
[System.Reflection.BindingFlags] 'NonPublic, Instance'
).Invoke($this, @())
}
}
# Sample call:
$obj = [MyClass]::new(); $obj.Name = 'foo'
$obj.CloneProblem()
As Santiago's answer notes, for a performant solution you need to cache the method definition obtained via reflection to avoid the performance hit that reflection entails.
To do so, you can take advantage of the fact that you can use ::
, the static member-access operator, directly on the automatic $this
variable, which refers to the class instance at hand, as an alternative to using ::
on the class (type), as would be the only option in C# code.
This is not only more convenient than having to repeat the class name in the form of a type literal ([MyClass]
), it also avoids the problem of hard-coding the return type, which would get in the way of subclassing (deriving from) your class.
($this::
is in effect short for $this.GetType()::
, and therefore avoids hard-coding a type (class) name).
class MyClass {
[string]$Name
# Static member variable in which to later cache the MemberwiseCone() method
# information.
hidden static [System.Reflection.MethodInfo] $s_cachedMethod
[object] CloneProblem() {
# On first call, perform reflection to get the method and also cache
# it for later use.
if (-not $this::s_cachedMethod) {
$this::s_cachedMethod = [object].GetMethod('MemberwiseClone', [System.Reflection.BindingFlags] 'NonPublic, Instance')
}
# Invoke the cached method:
return $this::s_cachedMethod.Invoke($this, @())
}
}
# Sample call via a subclass (derived class):
class MyClassToo : MyClass {} # sample subclass
# -> $true, proving that .CloneProblem() correctly returns an instance
# of the derived class.
[MyClassToo]::new().CloneProblem() -is [MyClassToo]
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744252629a4565239.html
评论列表(0条)