I need to create a ReadOnlyCollection from elements of an array but it seems like ReadOnlyCollection elements can only be defined in declaration of the collection. Is there any other way than listing each element of the array in the declaration of the collection as in the following sample?
[byte[]]$arr=10,20,30
[System.Collections.ObjectModel.ReadOnlyCollection[byte]]$readOnly=
$arr[0],$arr[1],$arr[2]
Thanks
Pass the array to the constructor instead:
$readOnly = New-Object 'System.Collections.ObjectModel.ReadOnlyCollection[byte]' -ArgumentList #(,$arr)
or (PowerShell 5.0 and up):
$readOnly = [System.Collections.ObjectModel.ReadOnlyCollection[byte]]::new($arr)
Now, your question title specifically says copy array elements - beware while you won't be able to modify $readOnly, its contents will still reflect changes to the array that it's wrapping:
PS C:\> $arr[0] = 100
PS C:\> $arr[0]
100
PS C:\> $readOnly[0]
100
If you need a completely separate read-only collection, copy the array to another array first and then overwrite the variable reference with the read-only collection:
$readOnly = [byte[]]::new($arr.Count)
$arr.CopyTo($readOnly, 0)
$readOnly = [System.Collections.ObjectModel.ReadOnlyCollection[byte]]::new($readOnly)
Now you can modify $arr without affecting $readOnly:
PS C:\> $arr[0] = 100
PS C:\> $arr[0]
100
PS C:\> $readOnly[0]
10
Mathias R. Jessen's helpful answer contains good alternatives and background information, but in your case you can simply assign your array to your type-constrained $readOnly variable:
[byte[]] $arr=10,20,30
[System.Collections.ObjectModel.ReadOnlyCollection[byte]] $readOnly = $arr
In PSv3+ you can alternatively use [Array]::AsReadOnly():
[byte[]] $arr=10,20,30
$readOnly = [Array]::AsReadOnly($arr) # shorter, but doesn't lock in the type of $readOnly
As stated, System.Collections.ObjectModel.ReadOnlyCollection<T> only wraps its input array, so later changes to the input array's elements would be reflected in the read-only collection.
However, whether it wraps your actual input array, depends on its specific type, because Powershell may create a new array for you behind the scenes - see this answer for details.
If in doubt, use $arr.Clone() to explicitly create a copy of your array.
Related
Can an array be used as the key in a hashtable? How can I reference the hashtable item with an array key?
PS C:\> $h = #{}
PS C:\> $h[#(1,2)] = 'a'
PS C:\> $h
Name Value
---- -----
{1, 2} a # looks like the key is a hash
PS C:\> $h[#(1,2)] # no hash entry
PS C:\> $h.Keys #
1
2
PS C:\> $h[#(1,2)] -eq 'a'
PS C:\> $h[#(1,2)] -eq 'b'
PS C:\> foreach ($key in $h.Keys) { $key.GetType() } # this is promising
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS C:\> $PSVersionTable.PSVersion.ToString()
7.1.4
While you can use arrays as hashtable keys, doing so is impractical:
Update: There is a way to make arrays work as hashtable keys, but it requires nontrivial effort during construction of the hashtable - see this answer.
You'll have the use the very same array instances as both the keys and for later lookups.
The reason is that arrays, which are instances of .NET reference types (as opposed to value types such as integers), use the default implementation of the .GetHashCode() method to return a hash code (as used in hashtables), and this default implementation returns a different code for each instance - even for two array instances that one would intuitively think of as "the same".
In other words: you'll run into the same problem trying to use instances of any such .NET reference type as hashtable keys, including other collection types - unless a given type happens to have a custom .GetHashCode() implementation that explicitly considers distinct instances equal based on their content.
Additionally, it makes use of PowerShell's indexer syntax ([...]) awkward, because the array instance must be nested, with the unary form of ,, the array constructor operator. However, dot notation (property access) works as usual.
$h = #{}
# The array-valued key.
$key = 1, 2
$h[$key] = 'a'
# IMPORTANT:
# The following lookups work, but only because
# the *very same array instance* is used for the lookup.
# Nesting required so that PowerShell doesn't think that
# *multiple* keys are being looked up.
$h[, $key]
# Dot notation works normally.
$h.$key
# Does NOT work, because a *different array instance* is used.
$h.#(1,2)
A simple test for whether a given expression results in the same hashtable lookup every time and is therefore suitable as a key is to call the .GetHashCode() method on it repeatedly; only if the same number is returned every time (in a given session) can the expression be used:
# Returns *different* numbers.
#(1, 2).GetHashCode()
#(1, 2).GetHashCode()
To inspect a given object or type for whether it is (an instance of) a .NET reference type vs. value type:
# $false is returned in both cases, confirming that the .NET array
# type is a *reference type*
#(1, 2).GetType().IsValueType
[Array].IsValueType
Workaround:
A workaround would be to use string representations of arrays, though coming up with unique (enough) ones may be a challenge.
In the simplest case, use PowerShell's string interpolation, which represents arrays as a space-separated list of the elements' (stringified) values; e.g. "$(1, 2)" yields verbatim 1 2:
$h = #{}
# The array to base the key on.
$array = 1, 2
# Use the *stringified* version as the key.
$h["$array"] = 'a'
# Works, because even different array instances with equal-valued
# instances of .NET primitive types stringify the same.
# '1 2'
$h["$(1, 2)"]
iRon points out that this simplistic approach can lead to ambiguity (e.g., a single '1 2' string would result in the same key as array 1, 2) and recommends the following instead:
a more advanced/explicit way for array keys would be:
joining their elements with a non-printable character; e.g.
$key = $array -join [char]27
or, for complex object array elements, serializing the array:
$key = [System.Management.Automation.PSSerializer]::Serialize($array)
Note that even the XML (string)-based serialization provided by the System.Management.Automation.PSSerializer class (used in PowerShell remoting and background jobs for cross-process marshaling) has its limits with respect to reliably distinguishing instances, because its recursion depth is limited - see this answer for more information; you can increase the depth on demand, but doing so can result in very large string representations.
A concrete example:
using namespace System.Management.Automation
$ht = #{}
# Use serialization on an array-valued key.
$ht[[PSSerializer]::Serialize(#(1, 2))] = 'a'
# Despite using a different array instance, this
# lookup succeeds, because the serialized representation is the same.
$ht[[PSSerializer]::Serialize(#(1, 2))] # -> 'a'
The primary cause of your problems here is that PowerShell's index access operator [] supports multi-index access by enumerating any array values passed.
To understand why, let's have a look at how the index accessor [...] actually works in PowerShell. Let's start with a simple hashtable, with 2 entries using scalar keys:
$ht = #{}
$ht['a'] = 'This is value A'
$ht['b'] = 'This is value B'
Now, let's inspect how it behaves!
Passing a scalar argument resolves to the value associated with the key represented by said argument, so far so good:
PS ~> $ht['a']
This is value A
But we can also pass an array argument, and all of a sudden PowerShell will try to resolve all items as individual keys:
PS ~> $ht[#('a', 'b')]
This is value A
This is value B
PS ~> $ht[#('b', 'a')] # let's try in reverse!
This is value B
This is value A
Now, to understand what happens in your example, let's try an add an entry with an array reference as the key, along with two other entries where the key is the individual values fround in the array:
$ht = #{}
$keys = 1,2
$ht[$keys[0]] = 'Value 1'
$ht[$keys[1]] = 'Value 2'
$ht[$keys] = 'Value 1,2'
And when we subsequently try to resolve the last entry using our array reference:
PS ~> $ht[$keys]
Value 1
Value 2
Oops! PowerShell unraveled the $keys array, and never actually attempted to resolve the entry associated with the key corresponding to the array reference in $keys.
In other words: The index accessor cannot be used to resolve dictionary entries by key is the key type is enumerable
So, how does one access an entry by array reference without having PowerShell unravel the array?
Use the IList.Item() parameterized property instead:
PS ~> $ht.Item($keys)
Value 1,2
I have the following dataset:
id|selectedquery|
1|SELECT fieldX FROM tableA|
2|SELECT fieldY FROM tableB|
that dataset is used in the following code
$rows=($dataSet.Tables | Select-Object -Expand Rows)
$i=0
foreach ($row in $rows)
{
#Write-Output $rows.selectquery[$i].length
$query = $rows.selectquery[$i]
#Write-Output $rows.selectquery[$i]
--doing some stuff--
$i++
}
Often $rows.selectquery[$i] only gives me the first character of the value in the field selectedquery being the 'S'.
When I remove the [$i] from $rows.selectquery it gives me (understandably) multiple records back. If I then put the [$i] back after $rows.selectquery[$i] things woerk fine.
Can anyone explain this behaviour?
You'll want to reference the SelectQuery property on either $row or $rows[$i] - not the entire $rows collection:
$row.SelectQuery
# or
$rows[$i].SelectQuery
Mathias' helpful answer shows the best way to solve your particular problem.
As for what happened:
You - inadvertently - used PowerShell's member-access enumeration feature when you used $rows.selectquery; that is, even though $rows is a collection that itself has no .selectquery property, PowerShell accessed that property on every element of the collection and returned the resulting values as an array.
The pitfall is that if the collection only has one element, the return value is not an array - it is just the one and only element's property value itself.
While this is analogous to how the pipeline operates (a single output object is captured by itself if assigned to a variable, while two or more are implicitly collected in an array), it is somewhat counterintuitive in the context of member-access enumeration:
In other words, $collection.SomeProperty is equivalent to $collection | ForEach-Object { $_.SomeProperty } and not, as would make more sense, because it always returns an array (collection), $collection.ForEach('SomeProperty')
GitHub issue #6802 discusses this problem.
While this behavior is often unproblematic, because PowerShell offers unified handling of scalars and collections (e.g. (42)[0], is the same as 42 itself; see this answer), a problem arises if the single value returned happens to be a string, because indexing into a string returns its characters.
Workaround: Cast to [array] before applying the index:
([array] $rows.selectquery)[0]
A simple example:
# Multi-element array.
[array] $rows1 = [pscustomobject] #{ selectquery = 'foo' },
[pscustomobject] #{ selectquery = 'bar' }
# Single-element array:
[array] $rows2 = [pscustomobject] #{ selectquery = 'baz' }
# Contrast member-access enumeration + index access between the two:
[pscustomobject] #{
MultiElement = $rows1.selectquery[0]
SingleElement = $rows2.selectquery[0]
SinglElementWithWorkaround = ([array] $rows2.selectquery)[0]
}
The above yields the following:
MultiElement SingleElement SinglElementWithWorkaround
------------ ------------- --------------------------
foo b baz
As you can see, the multi-element array worked as expected, because the member-access enumeration returned an array too, while the single-element array resulted in single string 'baz' being returned and 'baz'[0] returns its first character, 'b'.
Casting to [array] first avoids that problem (([array] $rows2.selectquery)[0]).
Using #(...), the array-subexpression operator - #($rows.selectquery)[0] - is another option, but, for the sake of efficiency, it should only be used on commands (e.g., #(Get-ChildItem -Name *.txt)[0]) not expressions, as in the case at hand.)
How can i delete row from array which is pscustomobject in a loop?
Getting errors if i use this in the loop:
$a = $a | where {condition to remove lines}
Getting the below error
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.
any suggetion to remove row from array.
Let me make some general points, given the generic title of the question:
Arrays (in .NET, which underlies PowerShell) are fixed-size data structures. As such, you cannot delete elements from them directly.
However, you can create a new array that is a copy of the original one with the unwanted elements omitted, and that is what the pipeline approach facilitates:
# Sample array.
$a = 1, 2, 3
# "Delete" element 2 from the array, which yields #(1, 3).
# #(...) ensures that the result is treated as an array even if only 1 element is returned.
$a = #($a | Where-Object { $_ -ne 2 })
PowerShell automatically captures the output from a pipeline in an array (of type [System.Object[]]) when you assign it to a variable.
However, since PowerShell automatically unwraps a single-element result, you need #(...), the array-subexpression operator to ensure that $a remains an array even if only a single element was returned - the alternative would be to type-constrain the variable as an array:
[array] $a = $a | Where-Object { $_ -ne 2 }
Note that even though the result is assigned back to input variable $a, $a now technically contains a new array (and the old one, if it is not being referenced elsewhere, will eventually be garbage-collected).
As for what you tried:
How can i delete row from array which is pscustomobject
As wOxxOm points out, [pscustomobject] are not arrays, but perhaps you meant to say that you have an array whose elements are custom objects, in which case the above approach applies.
Alternatively, if the array to delete elements from is stored in a property of a custom object, send that property's value through the pipeline instead, and assign the results back to that property.
The error message occurs when you try to use the + operator with a [pscustomobject] instance as the LHS, which is not supported; e.g.:
PS> ([pscustomobject] #{ foo = 'bar' }) + 1
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.
...
PowerShell doesn't know how to "add" something to a custom object, so it complains.
I often see the usage of # symbol in Powershell coding. Want to ask what is it used for? Example as below
$DistributionPointGroups = #("London")
#() is the array operator, which makes sure that even a single item (or zero) is returned as an array.
THE ARRAY SUB-EXPRESSION OPERATOR
The array sub-expression operator creates an array, even if it
contains zero or one object.
The syntax of the array operator is as follows:
#( ... )
You can use the array operator to create an array of zero or
one object.
PS C:\>$a = #("One")
PS C:\>$a.Count
1
PS C:\>$b = #()
PS C:\>$b.Count
0
Source: about_Arrays
I have a list of paths (filtered by special criteria). I want to mutate each entry in this list, but can't find a way (I think it's immutable). What's the best way of going about this?
Thanks
I guess you have some collection, not only list (probably array).
PS> $myList = 'first','second','third'
You can mutate the collection by indexing or just by creating new array like this:
PS> $myList[1] = '2nd'
#or
PS> $myList | % { $_.Substring(0,2) }
fi
se
th