Update: As @SeeminglyScience's states below, ignoring of exceptions seems to be limited to indexing with types that implement IDictionary<T1, T2>, in which potential exceptions are deliberately _avoided_ with .TryGetValue() calls.
An argument could be made that this behavior makes sense for the sake of symmetry with PowerShell's regular, non-generic (ordered) hashtables, which _themselves_ _ignore_ non-existent keys.
It does amount to PowerShell modifying the behavior of .NET types in a non-obvious manner, however.
At least with Set-StrictMode -Version 3 or higher, using an array's indexer with an out-of-bounds index results in a statement-terminating error.
The docs state with respect to version 3:
Prohibit out of bounds or unresolvable array indexes.
By contrast, _other exceptions appear to be quietly ignored_, irrespective of the effective Set-StrictMode setting.
A follow-up question is: should exceptions _other than_ System.IndexOutOfRangeException and System.Collections.Generic.KeyNotFoundException _always_ be surfaced?
Other types of exceptions are probably rare, but they do occur; e.g.:
# Note: Called via .Item() instead of indexer syntax ([...]),
# to ensure that the exception surfaces.
# Throws a System.ArgumentException
[Newtonsoft.Json.Linq.JObject]::Parse('{"foo":1}').Item([datetime]::now)
Set-StrictMode -Version 3
# OK - exception (statement-terminating error)
{ (0,1)[2] } | should -Throw -ErrorId System.IndexOutOfRangeException
# Does NOT throw an exception, even though the underlying type does.
# Note: The non-generic [hashtable] type (@{ ... }) does NOT throw an exception,
# but System.Collections.Generic.Dictionary<T, T> does.
{ $dt = [Collections.Generic.Dictionary[string, string]]::new(); $dt['nosuch'] } |
Should -Throw -ErrorId System.Collections.Generic.KeyNotFoundException
Both tests should pass.
The 2nd test fails:
Expected an exception, to be thrown, but no exception was thrown.
That is, the exception that occurs in the [System.Collections.Generic.Dictionary] indexer is quietly ignored.
You can surface it if you call the underlying .Item() method (parameterized property) directly:
$dt = [Collections.Generic.Dictionary[string, string]]::new(); $dt.Item('nosuch')
This reports the following error (ultimately a System.Collections.Generic.KeyNotFoundException exception):
Exception getting "Item": "The given key 'nosuch' was not present in the dictionary"
PowerShell Core 7.0.0-preview.4
@mklement0 similar is seen when accessing hashtable / dictionary keys with an invalid key.
Not sure that their behaviour is concretely defined with StrictMode enabled, but I would tend to think that StrictMode should surface that kind of exception as well for parity with how array indexes work. 馃憤
Thanks, @vexx32 - didn't know that about hashtables / dictionaries. Can you give a quick example?
Set-StrictMode -Version 3
$hashtable = @{}
$hashtable['nonexistentkey'] # no error
The same is true without StrictMode enabled, of course. Though I'm not sure that's technically the same, I guess? Since arrays have defined sizes, you can go outside their bounds, but dictionaries typically aren't a bounded set by design. It may be the same for the JObject indexer?
@vexx32:
It's not technically the same, because Hashtable _itself_ quietly ignores non-existent keys - no exception happens (except if you pass $null).
By contrast, the (directly implemented) JObject indexer _does_ throw an exception, which you can see if you call it via .Item():
# Throws exception, which surfaces due to calling via .Item()
[Newtonsoft.Json.Linq.JObject]::Parse('{"foo": "bar"}').Item([datetime]::now)
@mklement0 Dictionary<,> does though:
PS> $dt = [Collections.Generic.Dictionary[string, string]]::new()
PS> $dt['what']
# nothing
PS> $dt.get_Item('what')
Exception calling "get_Item" with "1" argument(s): "The given key 'what' was not present in the dictionary."
At line:1 char:1
+ $dt.get_Item('what')
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : KeyNotFoundException
Thanks, @SeeminglyScience - I've updated the OP with your example, because a generic dictionary is probably more common in PowerShell than Newtonsoft.Json.Linq.JObject; I've retained the latter as an example of where an indexer throws something _other_ than OutOfRangeException / KeyNotFoundException
@mklement0 here's where it happens:
I think it's pretty safe to say that it's by design. That expression generates code similar to:
try
{
return (object)IndexExpressionInExprVar();
}
catch (Exception)
{
if (Compiler.IsStrictMode(3))
{
throw;
}
}
return null;
Thanks, @SeeminglyScience, but that doesn't seem to be what's happening in practice:
Set-StrictMode -Version 3
# Despite strict mode v3, the following do *not* surface the underlying exceptions.
[Newtonsoft.Json.Linq.JObject]::Parse('{"foo":1}')[[datetime]::now]
[Collections.Generic.Dictionary[string, string]]::new()['nosuch']
Yeah IDictionary<,> follows a different pattern, it does TryGetValue instead:
Apparently JObject is also IDictionary<,>.
Thanks, @SeeminglyScience.
An argument could be made that this behavior makes sense for the sake of symmetry with PowerShell's regular, non-generic (ordered) hashtables, which themselves ignore non-existent keys.
It does amount to PowerShell modifying the behavior of .NET types in a non-obvious manner, however - so I've created https://github.com/MicrosoftDocs/PowerShell-Docs/issues/4868
That said, re-reading the docs, with respect to Set-StrictMode it really is only about _numeric_ _array_ indices, so I'm closing this.
Most helpful comment
@mklement0
Dictionary<,>does though: