class - Workaround `#24898`: MemberwiseClone is missing after upgrade to powershell 7.5.0 - Stack Overflow

I bounced into this issue: #24898: MemberwiseClone is missing after upgrade to powershell 7.5.0:class

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.

Share Improve this question edited Mar 24 at 12:48 iRon asked Mar 24 at 12:22 iRoniRon 24k10 gold badges58 silver badges99 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 2

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

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条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信