// Java
public abstract class Drawable {
public static Drawable createFromStream(IntputStream is, String srcName) {…}
// …
}
public interface Animatable {
public void start();
// …
}
/* package */ class SomeAnimatableDrawable extends Drawable implements Animatable {
// …
}
Further imagine that a call to Drawable.createFromStream() returns
an instance of SomeAnimatableDrawable.
What does the bindingDrawable.CreateFromStream() return?
// C#
var drawable = Drawable.CreateFromStream(input, name);
The binding Drawable.CreateFromStream() look at the runtime type
of the value returned, see that it's of type SomeAnimatableDrawable,
and look for an existing binding of that type. If no such binding is
found -- which will be the case here, as SomeAnimatableDrawable is
package-private -- then we check the value's base class, ad infinitum,
until we hit a type that we do have a binding for (or fail
catastrophically when we can't find a binding for java.lang.Object).
See also TypeManager.CreateInstance(), which is similar to the
code within JniRuntime.JniValueManager.GetPeerConstructor().
Any interfaces implemented by Java value are not consulted. Only
the base class hiearchy.
For the sake of discussion, assume that drawable will be an
instance of DrawableInvoker (e.g. 1adb7964), akin to:
internal class DrawableInvoker : Drawable {
// …
}
Further imagine that we want to invoke Animatable methods on
drawable. How do we do this?
This is where the .JavaCast<TResult>() extension method comes
in: we can use .JavaCast<TResult>() to perform a Java-side type
check the type cast, which returns a value which can be used to
invoke methods on the specified type:
var animatable = drawable.JavaCast<IAnimatable>();
animatable.Start();
The problem with .JavaCast<TResult>() is that it always throws on
failure:
var someOtherIface = drawable.JavaCast<ISomethingElse>();
// throws some exception…
@mattleibow requests an "exception-free JavaCast overload" so that he
can easily use type-specific functionality optionally.
Add the following extension methods on IJavaPeerable:
static class JavaPeerableExtensions {
public static TResult? JavaAs<TResult>(
this IJavaPeerable self);
public static bool TryJavaCast<TResult>(
this IJavaPeerable self,
out TResult? result);
}
The .JavaAs<TResult>() extension method mirrors the C# as
operator, returning null if the type coercion would fail.
This makes it useful for one-off invocations:
drawable.JavaAs<IAnimatable>()?.Start();
The .TryJavaCast<TResult>() extension method follows the
TryParse() pattern, returning true if the type coercion succeeds
and the output result parameter is non-null, and false otherwise.
This allows "nicely scoping" things within an if:
if (drawable.TryJavaCast<IAnimatable>(out var animatable)) {
animatable.Start();
// …
animatable.Stop();
}
Fixes: https://github.com/dotnet/java-interop/issues/10 Fixes: https://github.com/dotnet/android/issues/9038
Context: 1adb7964a2033c83c298c070f2d1ab896d92671b
Imagine the following Java type hierarchy:
Further imagine that a call to
Drawable.createFromStream()
returns an instance ofSomeAnimatableDrawable
.What does the binding
Drawable.CreateFromStream()
return?The binding
Drawable.CreateFromStream()
look at the runtime type of the value returned, see that it's of typeSomeAnimatableDrawable
, and look for an existing binding of that type. If no such binding is found -- which will be the case here, asSomeAnimatableDrawable
is package-private -- then we check the value's base class, ad infinitum, until we hit a type that we do have a binding for (or fail catastrophically when we can't find a binding forjava.lang.Object
). See alsoTypeManager.CreateInstance()
, which is similar to the code withinJniRuntime.JniValueManager.GetPeerConstructor()
.Any interfaces implemented by Java value are not consulted. Only the base class hiearchy.
For the sake of discussion, assume that
drawable
will be an instance ofDrawableInvoker
(e.g. 1adb7964), akin to:Further imagine that we want to invoke
Animatable
methods ondrawable
. How do we do this?This is where the
.JavaCast<TResult>()
extension method comes in: we can use.JavaCast<TResult>()
to perform a Java-side type check the type cast, which returns a value which can be used to invoke methods on the specified type:The problem with
.JavaCast<TResult>()
is that it always throws on failure:@mattleibow requests an "exception-free JavaCast overload" so that he can easily use type-specific functionality optionally.
Add the following extension methods on
IJavaPeerable
:The
.JavaAs<TResult>()
extension method mirrors the C#as
operator, returningnull
if the type coercion would fail. This makes it useful for one-off invocations:The
.TryJavaCast<TResult>()
extension method follows theTryParse()
pattern, returning true if the type coercion succeeds and the outputresult
parameter is non-null, and false otherwise. This allows "nicely scoping" things within anif
: