Skip to content

More specific method (indexer) overloads are not considered during overload resolution if they're part of an explicit interface implementation #10654

@mklement0

Description

@mklement0

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aResolution-AnsweredThe question is answered.WG-Enginecore PowerShell engine, interpreter, and runtime

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions