1

Suppose I have a list:

$DeletedUsers = New-Object System.Collections.Generic.List[System.object]

So I can easily add and remove users from the collection.

I want to be able to pass this list to a function that does something, but without modifying the original list, and it must stay of the same generic list type.

convertAll() seems to do exactly what I want without having to script out the creation of a new list myself with foreach-object, but I don't understand how to utilize the overload definitions (or quite understand what they mean).

There are many examples in C#, but I haven't been able to find one that demonstrates it in PoSH.

Example Scenario:

Assume $DeletedUsers contains a list of User objects of PSCustomObject type. With typical "User" properties such as department or Employment status. This list should be be capable of being passed to functions that will change statuses of the users property that can then be added to a separate output list of the same Generic.List type.

Currently any changes by the example function.

    Function ProcessUser {

    [Cmdletbinding()]
    Param($DeletedUsers)

    begin{$DeletedUsersClone = $($DeletedUsers).psobject.copy()} #OR similar

    process{
    $DeletedUsersClone | foreach { $_ | Add-Member -NotePropertyName 
    "Processed" -NotePropertyValue "Processed:00"; $Outputlist.add($_)} 
           }
                         }

Impacts the original $DeletedUsers, erroneously adding processed information to a list that should stay static.

There are alternate ways to prevent this from impacting the ultimate objective of the script, but the question is:

How do I create a True, non-referenced clone of a System.Collections.Generic.List[System.object] using built-in C# methods.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Phatmandrake
  • 187
  • 3
  • 15

2 Answers2

2

The trick is to use a scriptblock with an explicit cast to the delegate type. This looks like:

$DeletedUsers.ConvertAll([converter[object,object]] {param ($i) <# do convert #> })
Bruce Payette
  • 2,511
  • 10
  • 8
  • $DeletedUsersClone = $DeletedUsers.ConvertAll([converter[object,object]] { param ($DeletedUsers) $DeletedUsers }) Created a new list, but the objects being added to the list through the scriptblock appear to maintain their reference to the original list. Is there any means to divorce this relationship? – Phatmandrake Jun 04 '18 at 18:06
  • @Phatmandrake: Since your list elements are `[pscustomobject]` instances, you can use the following to create shallow clones of them: `$DeletedUsers.ConvertAll([converter[object,object]] {param ($obj) $obj.psobject.copy() })`. By contrast, your code simply creates a shallow clone of the _list itself_, and since `[pscustomobject]` is a reference type, the cloned list references the very same objects as the original. – mklement0 Jun 04 '18 at 18:12
  • 1
    I've scoured https://msdn.microsoft.com/en-us/library/s6hkc2c4(v=vs.110).aspx for other potential methods and I believe this to be the most elegant solution for creating a deep copy (?) of a Generic.List consisting of PSCustomObjects. – Phatmandrake Jun 04 '18 at 19:14
1

Note:

  • As became clear later, the OP is looking for a deep clone of the original list; i.e., not only should the list as a whole be cloned, but also its elements.

  • This answer only shows how to create a shallow clone (and how to pass a list read-only).

    • See Bruce Payette's helpful answer for a deep-cloning approach based on the .ConvertAll method; with [pscustomobject] instances, you'd use the following (but note that .psobject.Copy() only creates shallow copies of the [pscustomobject] instances themselves):

      $DeletedUsers.ConvertAll([converter[pscustomobject, pscustomobject]] {param ($obj) $obj.psobject.copy() })
      

  • If you want to pass a shallow clone of your list to a callee:

    • Pass [Collections.Generic.List[object]]::new($DeletedUsers) (PSv5+ syntax)
    • Alternatively, if the type of the list elements isn't known or if you don't want to repeat it, pass: $DeletedUsers.GetRange(0, $DeletedUsers.Count)
  • If you just want to prevent accidental modification of your list by a callee:

    • Pass $DeletedUsers.AsReadOnly() - however, that does change the type, namely to [Collections.ObjectModel.ReadOnlyCollection[object]]
mklement0
  • 382,024
  • 64
  • 607
  • 775