I have some read-only variables set with Set-Variable myVar -Value 10 -Option ReadOnly
syntax but I need read-only arrays too. How do I make read-only arrays?
Thanks
I have some read-only variables set with Set-Variable myVar -Value 10 -Option ReadOnly
syntax but I need read-only arrays too. How do I make read-only arrays?
Thanks
PetSerAl, as countless times before, has provided the solution in a terse comment on the question; he's also helped to improve this answer:
Use [Array]::AsReadOnly($someArray)
, available in PSv3+[1]
:
# Create a 2-element read-only collection containing strings.
$readOnlyColl = [Array]::AsReadOnly(('one', 'two'))
# Try to modify an element, which now fails:
$readOnlyColl[0] = 'uno'
In PSv2, use a cast to [System.Collections.ObjectModel.ReadOnlyCollection[<type>]]
, as shown below.
The above yields the following, demonstrating that elements cannot be modified:
Unable to index into an object of type System.Collections.ObjectModel.ReadOnlyCollection`1[System.String].
Note that the error message is somewhat misleading, because it is only write access via indexing that isn't allowed, read access (e.g., $readOnlyColl[0]
) works just fine.
While $readOnlyColl
is not strictly an array, it behaves like one
for all practical purposes.
Technically, $readOnlyColl
is an instance of generic type System.Collections.ObjectModel.ReadOnlyCollection`1
using an element type inferred from the input array's elements.[2]
Caveat: The return collection is only a wrapper around an array and, depending on the specific input array and casts you apply, later modifications to the input array's elements could be reflected in the wrapper collection.[3]
If you want to control the elements' data type explicitly, use a cast.
E.g., to create a [string]
-typed collection from [int]
values:
# Cast to an array of the desired type first.
[Array]::AsReadOnly([string[]] (1, 2, 3)
# Alternatively, cast to the target collection type directly.
# Always use this in PSv2, where [Array]::AsReadOnly() cannot be called.
[System.Collections.ObjectModel.ReadOnlyCollection[string]] (1, 2, 3)
Use [object[]]
/ [object]
for [object]
-typed elements, as with regular PowerShell arrays, but note that if the input array is indeed already an [object[]]
array, the resulting collection will be a wrapper around the array - see footnote [2].
Just to spell out why Set-Variable myVar -Option ReadOnly
is not enough to create a read-only array: it makes the variable read-only, meaning that you cannot assign a different value to it; by contrast, modifying a property of the data that happens to be stored in the variable - such as an array's element - is not prevented.
[1] The method has been available since .NET v2, on which PSv2 is built; however, only PSv3 introduced the ability to call generic methods directly, so PSv3+ is required; however, in PSv2 you can cast to [System.Collections.ObjectModel.ReadOnlyCollection[<type>]]
directly.
[2] That is, even though PowerShell creates [object[]]
arrays by default, if the actual elements happen to be all of the same type, PowerShell chooses that specific type rather than [object]
; in the case at hand, because both elements of the input array were strings, a new [string[]
typed array was created behind the scenes, which the resulting read-only collection wraps as type [System.Collections.ObjectModel.ReadOnlyCollection[string]]
.
[3] Because the collection is only a wrapper around the input array, anyone with access to the input array could still modify its elements and the wrapper collection would reflect that change.
In PowerShell, however, you're often shielded from this potential problem if PowerShell happens to create a new array behind the scenes that it then passes to [Array]::AsReadOnly()
. A new array is created for PS arrays ([object[]]
) whose elements all happen to have the same type, assuming you don't explicitly cast such an array to [object[]]
in the [Array]::AsReadOnly()
call. For a specifically typed input array (e.g., [int[]]
), a new array is only created if you cast to a different type (e.g., [string[]]
); demonstration of the wrapper problem if no new array is created:
$arr = [int[]] (1..3); $coll = [Array]::AsReadOnly($arr); $arr[1] = 42; $coll[1]
- $coll[1]
now reflects 42
, i.e., the changed value that was assigned via the underlying array, $a
.