locked
Object values in array changing by reference - don't want changes. RRS feed

  • Question

  • Having a problem with object(s).
    I want to use an object as a set of default values, make copies of the object, modify selected attributes and collect the modified objects in an array.
    The code, below, shows a simplified version.  An object is created and passed to a function which copies it and modifies attributes.  The Write-Host output confirms the modified attribute values.  The modified object is added to an array.  After the process is complete, the array consists of multiple instances, all with the values of the last object modified.  I guess this is a "by reference/by value" issue, but how do I get the array members to retain their "original" values?
    ################################################
    Script Output:
     Saturday, December 15, 2018 4:56:05 PM
     FTP1 Local1
     FTP2 Local2
     FTP3 Local3
     FTP4 Local4
     FTP5 Local5
     FTP5 Local5
     FTP5 Local5
     FTP5 Local5
     FTP5 Local5
     FTP5 Local5
     Done...
    ################################################
    Sample Script:
     Function Foo()
     { Param( $Defaults )
      $colReturn = @()
      For( $X = 1; $X -le 5; $X ++ )
      { $oTemp           = $Defaults
       $oTemp.FtpPath   = "FTP$X"
       $oTemp.LocalPath = "Local$X"
       $colReturn      += $oTemp
       
       Write-Host $oTemp.FtpPath  $oTemp.LocalPath #$strOut
      }#Next
      Write-Host
      ForEach( $Y IN $colReturn ){ Write-Host $Y.FtpPath  $Y.LocalPath }
     }#End Foo
     #########Main
     CLS
     Get-Date
     $oDefaults = New-Object System.Object
     $oDefaults | Add-Member -Type  NoteProperty `
           -Name  FtpPath  `
           -Value 'FtpDefault' 
     $oDefaults | Add-Member -Type  NoteProperty  `
           -Name  LocalPath  `
           -Value 'LocalDefault'
     $colResponse = Foo -Defaults $oDefaults
     "`r`nDone..."

    Monday, December 17, 2018 2:31 PM

Answers

  • I don't know to what class your object might be. If it's a PSObject/PSCustomObject, see below.

    If you only need a shallow copy then you can do this:

    $Defaults = New-Object PSObject -Property @{'Something'='Whatever';'FtpPath'=$null;'LocalPath'=$null}
    
    $colReturn = @()
    1..5|Foreach {
        $otemp = $Defaults.psobject.Copy()
        $otemp.FtpPath = "Ftp$_"
        $otemp.LocalPath = "Local$_"
        $colReturn += $otemp
    }
    $colReturn

    If the Copy doesn't work you can also try this:

    $Defaults = New-Object PSObject -Property @{'Something'='Whatever';'FtpPath'=$null;'LocalPath'=$null}
    
    $colReturn = @()
    1..5 | Foreach {
        $otemp = New-Object PsObject
        $Defaults.psobject.Properties | ForEach { 
            Add-Member -MemberType NoteProperty -InputObject $otemp -Name $_.Name -Value $_.Value
        }
        $otemp.FtpPath = "Ftp$_"
        $otemp.LocalPath = "Local$_"
        $colReturn += $otemp
    }
    $colReturn


    --- Rich Matheisen MCSE&I, Exchange Ex-MVP (16 years)


    Tuesday, December 18, 2018 8:36 PM

All replies

  • Inside that "For" loop you're only creating the $oTemp variable once. Because it's being assigned an object (a reference) the modifications happen to the same memory (referred to by the reference) area.

    The object you're passing to the function looks like it's pretty simple so using its inherited "clone" method (from the System.Object parent) should give you a new $oTemp with the values from the $Defaults parameter. E.g. $oTemp = $Defaults.Clone()

    Pushing that cloned (and then modified) object onto the array should give you what you're looking for.


    --- Rich Matheisen MCSE&I, Exchange Ex-MVP (16 years)



    Monday, December 17, 2018 4:06 PM
  • Thanks for the quick reply.

    I understand the problem/symptom, and your proposed solution sounded great, but...

    Even though MSDN Docs says every .Net object derives from the Object class and should have the relevant methods, the initial object I create does not have a Clone(), MemberwiseClone() or Copy() method.  Initially I created a System.Object, and also tried a PSObject.  They have the same four methods:

    Name        MemberType Definition                   
    ----        ---------- ----------                   
    Equals      Method     bool Equals(System.Object obj)
    GetHashCode Method     int GetHashCode()            
    GetType     Method     type GetType()               
    ToString    Method     string ToString()

    So it looks like my solution must be to create a new object, create properties and programmatically transfer values.

    Any thoughts on how I might create an object with a Clone() method?

    Tuesday, December 18, 2018 4:22 PM
  • I don't know to what class your object might be. If it's a PSObject/PSCustomObject, see below.

    If you only need a shallow copy then you can do this:

    $Defaults = New-Object PSObject -Property @{'Something'='Whatever';'FtpPath'=$null;'LocalPath'=$null}
    
    $colReturn = @()
    1..5|Foreach {
        $otemp = $Defaults.psobject.Copy()
        $otemp.FtpPath = "Ftp$_"
        $otemp.LocalPath = "Local$_"
        $colReturn += $otemp
    }
    $colReturn

    If the Copy doesn't work you can also try this:

    $Defaults = New-Object PSObject -Property @{'Something'='Whatever';'FtpPath'=$null;'LocalPath'=$null}
    
    $colReturn = @()
    1..5 | Foreach {
        $otemp = New-Object PsObject
        $Defaults.psobject.Properties | ForEach { 
            Add-Member -MemberType NoteProperty -InputObject $otemp -Name $_.Name -Value $_.Value
        }
        $otemp.FtpPath = "Ftp$_"
        $otemp.LocalPath = "Local$_"
        $colReturn += $otemp
    }
    $colReturn


    --- Rich Matheisen MCSE&I, Exchange Ex-MVP (16 years)


    Tuesday, December 18, 2018 8:36 PM
  • Thanks Rich.  I marked yours as answer.

    I did something similar, creating a function that clones an object with some flexibility.

    Too bad we couldn't create the clone in place...

    Thursday, December 20, 2018 10:10 PM