Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java-like virtual extension methods/default interface implementations, aka traits or (ruby-like) mixins #73

Closed
fschmied opened this issue Jan 25, 2015 · 3 comments
Assignees
Labels

Comments

@fschmied
Copy link

Previous discussion on CodePlex: https://roslyn.codeplex.com/discussions/577994

Excerpts from CodePlex discussion that aid in explaining the feature:

I've just posted a new language feature request to UserVoice about adding default method implementations to interfaces, also known as "virtual extension methods", similar to traits or mixins in other languages. When writing frameworks, such a feature would often make much more sense than to provide an abstract base class with the default implementations because there is only one base class while interfaces can be combined with much more flexibility.

However, I'm wondering if this hasn't been discussed before, since it's a somewhat obvious feature now that Java 8 has it. I haven't found anything via Google or CodePlex, am I missing something?

[...]

Java added them as an alternative to extension methods, but they really have a very different feature set, with different purposes.

Extension methods:
Allow methods to be added to existing interfaces from the outside.

  • Are great for adding APIs with default implementations that feel like instance methods to existing interfaces.
  • Are really static, so they do not provide a way to override behavior added by extension methods in an object-oriented sense.
  • Cause conflicts when a class has the same APIs both via an (interface-based) extension method and in the implementation. The compiler chooses to prefer the instance variant, so a class instance behaves differently depending on the static type of the variable on which the API is invoked.

Virtual extension methods:

  • Allow interfaces to bring a default implementation (similar to an abstract base class, but without the restriction on linear inheritance).
  • Are great for providing "right in 90% of the cases" APIs, while still allowing object-oriented extensibility.
  • Must be defined on the interface, so they do not provide a way to add behavior by a third-party component.
  • Cause conflicts when two interfaces bring default imlementations. The compiler could warn and choose one (like in Ruby; e.g., the last one in the interface list) or it could raise an error and have the developer implement the method (like in Java 8).

The classic way to provide default implementations while supporting extensibility in C# is to provide an abstract base class. This has a lot of restrictions because base classes can only follow one dimension (i.e., only one base class per class), so the classic second option is to provide the default implementation as a separate class and delegating to it. This second option, however, can be quite cumbersome if it's about more than just one interface member, and it's "mechanical" code that the compiler could automatically generate.

We actually had this situation recently about a feature of a framework a colleague of mine is implementing. That framework defines a base class hierarchy according to a certain "is a" dimension. Framework clients derive from those base classes. The framework additionally wants to offer other features to be mixed into those classes. Implementing this with normal extension methods is only possible as long as the other features never need to be adapted ("overridden"). In the sense of object-orientation, this is crippling - it doesn't allow us to be open for extension by subclasses. So composition and delegation was chosen instead, forcing (many) framework clients to write mechanical delegation code...

While there are workarounds (the extension method could perform a dynamic type check or reflection on its target and decide whether to delegate back to the target class), they are exactly that: workarounds. Languages like Ruby, Python, and, now, Java have mixin mechanisms for such scenarios, which solve the problem much more elegantly.

Virtual extension methods can, BTW, be a pure C# feature, they don't need runtime extensions (unless you want to be able to add interface members while retaining compatibility of compiled code). When compiling an interface with a default member, the C# compiler could move it to a separate (compiler-generated) class. When compiling a class implementing such an interface but omitting the default member, the C# compiler could auto-implement it to call into the compiler-generated class. A custom attribute on the interface member could be used as metadata describing the relationship between the interface member and its default implementation.

So, to clarify with an example, consider the following code:

interface ISomeInterface
{
  string Property { get; }

  default string Format()
  {
    return string.Format ("{0} ({1})", GetType().Name, Property);
  }
}

class SomeClass : ISomeInterface
{
  public string Property { get; set; }
}

This could be translated as if it had been written as follows:

interface ISomeInterface
{
  string Property { get; }

  [DefaultImplementation (typeof (ISomeInterface_DefaultMembers), Method = "...")]
  string Format();
}

class ISomeInterface_DefaultMembers
{
  public static string Format (ISomeInterface obj)
  {
    return string.Format ("{0} ({1})", obj.GetType().Name, obj.Property);
  }
}

class SomeClass : ISomeInterface
{
  public string Property { get; set; }

  public string Format()
  {
    return ISomeInterface_DefaultMembers.Format (this);
  }
}
@theoy theoy added this to the Unknown milestone Jan 26, 2015
@HaloFour
Copy link

I like the concept but not the implementation. I think that a feature like this should be properly supported by the CLR so that any language can understand and benefit from it.

I do think that this feature would make it much simpler to implement a number of interfaces that have additional boilerplate members. For example, the non-generic methods of IEnumerable<T> and IEnumerator<T> could have a default implementation given that they almost always just call their generic counterparts.

@panesofglass
Copy link

I also agree with the general idea but not necessarily the proposed implementation. This seems very similar to the proposals for protocols and extension interfaces. I think these are closer to Scala's traits than the above proposal. This could/should also allow you to adapt classes you don't own to match a given interface and codegen an adapter that would be used at the call site.

@gafter
Copy link
Member

gafter commented Sep 13, 2015

We're tracking this in #258.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants