question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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

See original GitHub issue

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

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:19 (10 by maintainers)

github_iconTop GitHub Comments

2reactions
mklement0commented, Oct 2, 2019

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.

2reactions
SeeminglySciencecommented, Oct 2, 2019

If an overload match is found as a direct member, explicit interface implementations aren’t consulted for more specific matches - this is by design, not least because changing this would be a performance concern.

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.

The latter is currently broken in some situations, presumably relating to types that implement IDynamicMetaObjectProvider, so a fix is needed.

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why Explicit Implementation of a Interface can not be public?
The reason for an explicit interface implementation is to avoid name collisions with the end result being that the object must be explicitly ......
Read more >
Explicit Interface Implementation - C# Programming Guide
An explicit interface implementation doesn't have an access modifier since it isn't accessible as a member of the type it's defined in.
Read more >
Code quality rules overview - .NET
It does not override Object.Equals nor does it overload the language-specific operator for equality, inequality, less than, or greater than.
Read more >
Overload resolution - Kotlin language specification
Kotlin supports overloading for callables and properties, that is, the ability for several callables (functions or function-like properties) or properties ...
Read more >
Implicit conversions - cppreference.com
If there are multiple overloads of the function or operator being called, after the implicit conversion sequence is built from T1 to each ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found