More specific method (indexer) overloads are not considered during overload resolution if they're part of an explicit interface implementation
See original GitHub issuePlease 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
Issue Analytics
- State:
- Created 4 years ago
- Comments:19 (10 by maintainers)
Understood re design intent and thanks for clarifying, @SeeminglyScience.
Now that I understand the issue better and now that I know that casting to an interface generally works, I’m not personally interested in pursuing this aspect further.
Instead I’ve created a docs issue at https://github.com/MicrosoftDocs/PowerShell-Docs/issues/4871
I’ve opened a new issue focused on fixing the broken interface casting at #10688
I’m closing this.
Just to be clear, I did purposefully add the check for explicit implementations only as a fallback, but truthfully I wasn’t considering the scenario of calculating the best overload to use. It was more of a “if we already found one, we probably don’t need to find more” type of thing. Would it be a performance hit? Yeah for sure. Would it be impactful? Ehhhh only benchmarks could tell. The binder is cached so I would think it would be pretty minimal.
What I’m trying to get at is that saying it’s by design on my part would be generous. I think it mostly makes sense the way it is, or rather not particularly worth changing. But if you disagree I encourage you to switch it up in the binder and run some benchmarks.
A fix is needed for sure, what’s unclear is who needs to fix what. It seems bizarre to me that their convert binder wouldn’t account for interfaces. Maybe PowerShell should swallow the exception depending on where it happens. Maybe it should approach conversions for IDMOP more delicately, but imo that’s kind of the point of
IDMOP
, especially in PowerShell. My gut reaction is that the fix should be in Json.NET but I haven’t looked closely enough at the PowerShell side to see if there’s something better it could do there.