Repository navigation
Java 8 Interface Binding #25
Description
Android N uses new Java 8 language features such as interface default methods and interface static methods, which are features that tools/generator hasn't had to deal with before.
The question: how should they be dealt with?
// Java
public interface HelloJava8 {
public static final int VALUE = 42;
public void a ();
public default void defaultMethod() {
}
public static void staticMethod() {
}
}There are at least three ways to do so:
- Ignore them.
- Expose them.
- Treat them specially.
Ignore them
Interface default methods don't need to be implemented, and static methods aren't implementable. The binding mechanism could thus simply ignore them entirely.
// C#
public interface IHelloJava8 {
public void A ();
}
public static class HelloJava8 {
public const int Value = 42;
}This isn't entirely desirable, because it also means default interface methods can't be called.
IHelloJava8 value = ...
value.DefaultMethod(); // error
// No way to invoke HelloJava8.staticMethod()On the plus side, implementors don't need to worry about them:
public class WootJava8 : Java.Lang.Object, IHelloJava8 {
public void A () {}
}Expose them
The "simple" version of "expose them" is do the simple thing: static methods are bound in the constant-containing static class, and default methods are bound as normal methods:
// C#
public interface IHelloJava8 {
public void A ();
public void DefaultMethod ();
}
public static partial class HelloJava8 {
public const int Value = 42;
public void StaticMethod ()
{...}
}This allows calling both methods:
IHelloJava8 value = ...
value.DefaultMethod(); // works
HelloJava8.StaticMethod(); // worksHowever, this means that C# subclasses need to implement all the default methods:
public class WootJava8 : Java.Lang.Object, IHelloJava8 {
public void A () {}
public void DefaultMethod () {...}
}This might not seem bad in this example, but java.util.Collection contains five default methods (one inherited from Iterable).
This might be fine and acceptable, and we could help this scenario by providing access to the default method implementation:
public static partial class HelloJava8 {
public void InvokeDefaultMethod(IHelloJava8 self)
{
/* Non-virtual invocation of HelloJava8.defaultMethod() */
}
}
public partial class WootJava8 {
public void DefaultMethod ()
{
HelloJava8.InvokeDefaultMethod (this);
}
}However, such "help" will require documentation and training to know about, complicating use.
Treat them specially
To a large extent, the concern is around C# implementations of the interface. It is seen that requiring that optional methods be implemented will complicate the C# experience. Perhaps we can fix this by splitting out the optional methods?
Then, we can use extension methods to invoke the default methods:
// C#
public interface IHelloJava8 {
public void A ();
}
public static partial class HelloJava8 {
public const int Value = 42;
public void StaticMethod ()
{...}
public void DefaultMethod (this IHelloJava8 self)
{
// *Virtual* dispatch to invoke self.defaultMethod()
}
public interface IDefaultMethods {
public void DefaultMethod ();
}
}This allows calling both methods:
IHelloJava8 value = ...
value.DefaultMethod(); // works via extension method
HelloJava8.StaticMethod(); // worksIt also means that C# types don't need to implement the default methods:
public partial class WootJava8 : Java.Lang.Object, IHelloJava8 {
public void A () {}
}...but if the C# type does want to implement them, they can all be implemented by using the IDefaultMethods interface:
public partial class WootJava8 : HelloJava8.IDefaultMethods {
public void DefaultMethod () {}
}
Activity
jonpryor commentedon Apr 29, 2016
At present, I'm leaning toward (2) Expose Them, if only because it's implementation is simple (though the documentation effort regarding the presence of
HelloJava8.InvokeDefaultMethod()isn't simple).mtilbrook-dev commentedon Apr 30, 2016
I don't think ignoring them is an option.
I don't like (2) Expose Them. It adds unneeded name mangling. The documentation issue is also a major challenge.
I like (3) Treat them specially, It feels much cleaner from a consumer perspective.
jonpryor commentedon May 1, 2016
I don't understand what you mean by "unneeded name mangling." What name mangling? The
Invokehelpers?Agreed. However, (3) also has documentation issues, just different documentation issues.
mtilbrook-dev commentedon May 2, 2016
Yes, the
Invokehelper. Mangling might not have been the right language there.What documentation do you think will be needed? I can understand that (2)
InvokeRequiring docsjonpryor commentedon May 2, 2016
The presence and use of the
IDefaultMethodsinterface. It'll be a situation of "Java declares aHelloJava8.defaultMethod()interface method, and I can call that method from C#, but how do I implement that method in C#? It's not present in theIHelloJava8interface." It's in theHelloJava8.IDefaultMethodsinterface, which code completion won't know about, and who's going to know to look for that interface without...documentation. :-)(2) answers that question: it's an interface method, therefore you implement it. It's only when you don't want to implement the default interface method that documentation is required, or when you want to invoke the default behavior.
Both (2) and (3) raise the question of what to do with our "missing abstract method generator" (@radekdoulik? ;-) -- presumably it should check for and use the default interface method if present, but we'd need a way for the generator to know that (likely a new custom attribute).
[generator] Disable interface default method binding
jonpryor commentedon Jun 24, 2016
Ignore them and Treat them specially don't interact well with Java versioning:
defaultinterface methods aren't restricted to new methods; existing methods can be madedefault!Case in point: [
java.lang.reflect.AnnotatedElement.html.isAnnotationPresent()](https://developer.android.com/reference/java/lang/reflect/AnnotatedElement.html#isAnnotationPresent%28java.lang.Class<? extends java.lang.annotation.Annotation>%29) is non-defaultin API-23 and is nowdefaultin API-24. The Ignore them and Treat them specially approaches result in removing theIsAnnotationPresent()method from theIAnnotatedElementinterface, which would break all implementations.This can be fixed in
Mono.Android.dll-- we know what the previous API is, we can manually fixup such removed members -- but I'm dubious of the effects on 3rd party.jarbindings such as the Android Support library. I believe that most.jarbindings will consist of as little effort as possible to get a compiling binding, and if the.jarchanges interface methods to default interface methods between versions, this can break existing C# interface implementations.I'm very dubious of solutions (1) and (3) right now.
jonpryor commentedon Jun 24, 2016
The Expose them (2) approach has a different problem: if
defaultmethods are added to an interface over time, that means that previously built C# classes implementing the interface will break.Xamarin.Android 6.1 added some support to "fixup" added interface methods with an implementation that throws
AbstractMethodError, which made sense then, but doesn't make sense in the context of default interface methods.What we can instead do is place a custom attribute on the default method which refers to the class with the
Invokemethod wrapper:The abstract method fixup code could then fixup "missing" interface methods to invoke the default method:
During Xamarin.Android packaging, we would fix
MyHelloto be:This would extend the existing support for versioned interfaces in a natural manner, causing the generated method implementation to invoke the default method instead of throwing an exception.
atsushieno commentedon Jul 8, 2016
While all of those choices have problems, their severity on the issues are different.
I believe 1) is the only choice we can take with least problem so far (and that's what we do right now). It is the most conservative and the safest on future API breakage than other choices. By choosing 2) or 3) you would have already brought troubles regarding IAudioRouting fixes.
dotnet/android@576fb98
Default interface methods that "used to be non-default" can be "fixed" by binding authors to change "abstract" attribute on the method i.e.:
It should be also noted that those binding authors should be responsible to API changes. They can be the only responsible party.
It should be noted that once we expose extraneous methods to implement, especially at generator level, and we find any fatal problem (like we did for IAudioRouting), we cannot go back to the original state because it would already bring API breakage to everyone. It is the worst scenario.
Having said that 3) is worth implementing and probably even providing as an opt-in feature.
ddobrev commentedon Oct 12, 2018
Please fix because we cannot use the latest ExoPlayer.
atsushieno commentedon Oct 13, 2018
You should report specific, individual, actual issues as a new issue, not here without details.
The support for default interface methods is implemented at #341 (when I was at Xamarin)
[generator] All bound interfaces should inherit IJavaPeerable (#455)
Bump to xamarin/Java.Interop/master@29f9707 (#3542)
[generator] Support default interface methods. (#459)
[Xamarin.Android.Build.Tasks] MSBuild support for C#8 DIM emission (#…
[Xamarin.Android.Build.Tasks] MSBuild support for C#8 DIM emission (#…
brendanzagaeski commentedon Sep 25, 2019
Release status update
A new Preview version of Xamarin.Android has now been published on Windows that includes the new capabilities related to this item. See the release notes for additional details.
The new functionality is not yet available on macOS. I will update this item again when a Preview version with the new functionality is available on macOS.
Feature included in Xamarin.Android 10.0.99.100.
Feature included on Windows in Visual Studio 2019 version 16.4 Preview 1. To try the Preview version that includes the fix, check for the latest updates in Visual Studio Preview.
Feature not yet available on macOS.
brendanzagaeski commentedon Dec 4, 2019
Release status update
A new Release version of Xamarin.Android has now been published on Windows that includes the new capabilities related to this item. See the release notes for additional details.
The new functionality is not yet published in a Release version on macOS. I will update this item again when a Release version with the new functionality is available on macOS.
Feature included in Xamarin.Android 10.1.0.30.
Feature included on Windows in Visual Studio 2019 version 16.4. To get the new version that includes the feature, check for the latest updates or install the latest version from https://visualstudio.microsoft.com/downloads/.
(Feature also included on macOS in Visual Studio 2019 for Mac version 8.4 Preview 2.1 and higher. To try the Preview version that includes the feature, check for the latest updates on the Preview updater channel.)
brendanzagaeski commentedon Jan 8, 2020
Release status update
A new Release version of Xamarin.Android has now been published on macOS that includes the new capabilities related to this item. See the release notes for additional details.
Feature included in Xamarin.Android 10.1.1.0.
Feature included on macOS in Visual Studio 2019 for Mac version 8.4. To get the new version that includes the feature, check for the latest updates on the Stable updater channel.
[generator] Support default interface methods. (#459)
[generator] Support default interface methods. (dotnet#459)