Skip to content
This repository was archived by the owner on Jun 30, 2023. It is now read-only.
This repository was archived by the owner on Jun 30, 2023. It is now read-only.

Support for default interface implementations #113

@stakx

Description

@stakx

@kzu you asked me in #109 (comment) to open an issue regarding default interface implementations. I'm taking your word for them not currently being supported by this library.

At the IL level, there's nothing too surprising about them: they simply aren't marked abstract as interface methods usually are; they can have a method body like any other method. AFAIK interface methods are also no longer guaranteed to be declared public. Interfaces still cannot contain any instance fields, though IIRC they can now have static ones. Oh, and all of this is only supported starting with netcoreapp3.0 / netstandard2.1 runtimes.

At the reflection level, you'll notice that default implementations remain in the interface they're defined in. That is, a class implementing an interface with default impls doesn't inherit that implementation, you'll have to go look for it directly in the interface. Generally speaking, default impl integration with Reflection isn't terribly well done, there are some gotchas with GetInterfaceMap and detecting overrides etc. I could probably say more about this but will stop here for brevity.

At the C# language level, when implementing an interface, you can implement a method that has a default impl in the interface... but you don't have to. If you do choose to implement the method, then want to call the base implementation, you cannot just do base.Blah(), instead of base. you need to cast the this pointer to the interface with the desired default impl. I suppose the language designers chose this path to solve diamond inheritance issues where base might be ambiguous. They also came up with the concept of a "most specific override" rule.

If it's any help, I've recently added support for default interface impls to Moq 4 (see devlooped/moq#1130) — it works via CallBase, which will call the most specific override. Because DynamicProxy doesn't yet support them properly and Reflection also isn't terribly useful, I had to do a workaround using IL generation... it's not pretty, but may give you some insight into how those things work.

I hope your path using Roslyn will be a little less rocky. :-)

P.S. I should mention that instead of .CallBase() always picking the most specific override, one could consider a new .CallBase<TInterfaceWithImpl>() to allow user code to choose which base implementation should be called (as there could be several!). Meaning, there can be more than just one "target instance".

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions