-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Description
Please see #10688
If a given type implements the following method overloads:
- one with an
[object]-typed parameter - one with an
[int]-typed parameter
the more specifically typed [int] overload should take precedence over the [object]-typed one, if an [int] instance is passed on invocation, but that is not guaranteed to be the case and currently depends on the implementation details of the type at hand.
The problem is that a more specific overload that is part of an explicit interface implementation isn't considered if a match was found as a direct type member. (#7633 is related, but doesn't solve this problem, because it only consults the explicit interfaces if no direct member is found.) See @SeeminglyScience's comment below.
Note: A real-world example are the two indexers (parameterized .Item property) overloads that Json.NET's [JObject] type exposes: one from the type directly, for named indexing (property names), and one from the implementation of the IList[JToken] interface, for numeric indexing - the latter is ignored.
The .Item() overloads are:
PS> ([Newtonsoft.Json.Linq.JObject]::Parse('{"foo": "bar"}') | Get-Member Item).Definition -split ', '
Newtonsoft.Json.Linq.JToken Item(System.Object key) {get;set;}
Newtonsoft.Json.Linq.JToken IList[JToken].Item(int index) {get;set;}The only - impractical - workaround at the moment is to use reflection:
# Get the interface's parameterized [int]-parameter .Item() property that underlies the indexer.
$intIndexer = [System.Collections.Generic.IList[Newtonsoft.Json.Linq.JToken]].GetProperty('Item')
$obj = [Newtonsoft.Json.Linq.JObject]::Parse('{"foo": "bar"}')
# Call the indexer with an [int]
$intIndexer.GetValue($obj, 0).ToString()Steps to reproduce
Add-Type -TypeDefinition @'
// Interface with NUMERIC indexer.
public interface IFoo {
string this[int index] { get; }
}
// Class with [object] indexer and IMPLICIT IFoo implementation.
public class FooImplicit : IFoo {
public string this[object key] {
get => $"item with KEY {key}";
}
public string this[int index] {
get => $"item with INTEGER {index}";
}
}
// Class with [object] indexer and EXPLICIT IFoo implementation.
public class FooExplicit : IFoo {
public string this[object key] {
get => $"item with KEY {key}";
}
string IFoo.this[int index] {
get => $"item with INTEGER {index}";
}
}
'@
# OK - direct-member [int] overload takes precedence.
[FooImplicit]::new()[1] | Should -Match 'INTEGER'
# BROKEN - [int] overload from explicit interface method implementation is
# NOT used.
[FooExplicit]::new()[1] | Should -Match 'INTEGER'
# OK - explicit cast to interface [IFoo],
# !! but note that the same approach does NOT work with [JObject]
([IFoo] [FooExplicit]::new())[1] | Should -Match 'INTEGER'Expected behavior
All tests should pass.
Actual behavior
The 2nd test fails with:
Expected regular expression 'INTEGER' to match 'item with KEY 1', but it did not match.
That is, the more specific [int]-typed overload from the explicit interface implementation was not called.
Environment data
PowerShell Core 7.0.0-preview.4