You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #11769 (carved out of #11617) makes generated interface peer proxies derive from the non-generic JavaPeerProxy base instead of JavaPeerProxy<T>. This is required because closing JavaPeerProxy<[DynamicallyAccessedMembers(Constructors)] T> over a Java interface (which has no constructors) makes ILC fail to load the type:
Failed to load type 'Java.Interop.JavaPeerProxy`1<...INonMarshalingPreviewCallback>' from assembly 'Mono.Android'
A side effect of that fix: the non-generic JavaPeerProxy.GetContainerFactory() returns null for interface peers, so the AOT-safe container path in JavaConvert.TryGetFactoryBasedConverter cannot build JavaList<IInterface> / JavaCollection<IInterface> / JavaDictionary<…, IInterface>. A binding member that surfaces such a collection (e.g. one returning IList<ISomeListener>) falls back to Type.MakeGenericType(...), which is unavailable under NativeAOT.
Why we can't simply reuse the existing single-parameter container types
Three independent walls block JavaList<IInterface> from existing under ILC:
DAM wall — JavaList<T>, JavaCollection<T>, JavaDictionary<TKey,TValue>, and JavaPeerContainerFactory<T> all annotate their type parameter(s) with [DynamicallyAccessedMembers(PublicConstructors | NonPublicConstructors)]. Instantiating any of them over a ctor-less interface fails ILC the same way JavaPeerProxy<IInterface> does.
Reflection wall — the DAM is load-bearing, not redundant: ValueManager.GetPeer reflects on the activation constructor for the cases the typemap cannot proxy (constructed generics). You can't reflect-construct an interface.
Variance wall — substituting the invoker as the single type argument (JavaList<TInvoker> = IList<TInvoker>) does not satisfy the user-facing IList<TInterface>, because C# generics are invariant.
Separate the presented element type from the constructed element type:
internalsealedclassJavaList<TInterface,[DynamicallyAccessedMembers(Constructors)]TInvoker>:JavaList,IList<TInterface>whereTInvoker:TInterface{// The DAM is on TInvoker (a concrete invoker that HAS constructors), so ILC can load it.// Construction goes through the real ctor (no reflection on the interface), and the value// is presented as TInterface (TInvoker : TInterface) so it fits IList<TInterface>.internalTInterface?InternalGet(intlocation)=>JavaConvert.FromJniHandle<TInvoker>(InternalGetReference(location).Handle,JniHandleOwnership.TransferLocalRef);// ...}
DAM wall → solved: the DAM lands on TInvoker, which has constructors.
Reflection wall → solved: JavaConvert.FromJniHandle<TInvoker> constructs the concrete invoker.
Variance wall → solved: the type is IList<TInterface>.
The generated JavaPeerContainerFactory for an interface peer already has both types available (the proxy carries TargetType = the interface and InvokerType = the invoker), so it can instantiate JavaList<TInterface, TInvoker> directly. Base-class virtual methods can route to the correctly DAM-annotated concrete type so the single-parameter generic container types are never instantiated over an interface.
Equivalent two-parameter forms are needed for:
JavaCollection<TInterface, TInvoker>
JavaDictionary<…> for interface keys and/or values (potentially four combinations, or a key/value-invoker pair)
Interface-peer JavaPeerContainerFactory produces the two-parameter containers (and GetContainerFactory() no longer returns null for interfaces)
A NativeAOT test that round-trips a Java collection of a binding interface element (e.g. IList<ISomeListener>) with no MakeGenericType / reflection on the interface
Background
PR #11769 (carved out of #11617) makes generated interface peer proxies derive from the non-generic
JavaPeerProxybase instead ofJavaPeerProxy<T>. This is required because closingJavaPeerProxy<[DynamicallyAccessedMembers(Constructors)] T>over a Java interface (which has no constructors) makes ILC fail to load the type:A side effect of that fix: the non-generic
JavaPeerProxy.GetContainerFactory()returnsnullfor interface peers, so the AOT-safe container path inJavaConvert.TryGetFactoryBasedConvertercannot buildJavaList<IInterface>/JavaCollection<IInterface>/JavaDictionary<…, IInterface>. A binding member that surfaces such a collection (e.g. one returningIList<ISomeListener>) falls back toType.MakeGenericType(...), which is unavailable under NativeAOT.Why we can't simply reuse the existing single-parameter container types
Three independent walls block
JavaList<IInterface>from existing under ILC:JavaList<T>,JavaCollection<T>,JavaDictionary<TKey,TValue>, andJavaPeerContainerFactory<T>all annotate their type parameter(s) with[DynamicallyAccessedMembers(PublicConstructors | NonPublicConstructors)]. Instantiating any of them over a ctor-less interface fails ILC the same wayJavaPeerProxy<IInterface>does.ValueManager.GetPeerreflects on the activation constructor for the cases the typemap cannot proxy (constructed generics). You can't reflect-construct an interface.JavaList<TInvoker>=IList<TInvoker>) does not satisfy the user-facingIList<TInterface>, because C# generics are invariant.Proposed solution: two-type-parameter container types
Separate the presented element type from the constructed element type:
TInvoker, which has constructors.JavaConvert.FromJniHandle<TInvoker>constructs the concrete invoker.IList<TInterface>.The generated
JavaPeerContainerFactoryfor an interface peer already has both types available (the proxy carriesTargetType= the interface andInvokerType= the invoker), so it can instantiateJavaList<TInterface, TInvoker>directly. Base-class virtual methods can route to the correctly DAM-annotated concrete type so the single-parameter generic container types are never instantiated over an interface.Equivalent two-parameter forms are needed for:
JavaCollection<TInterface, TInvoker>JavaDictionary<…>for interface keys and/or values (potentially four combinations, or a key/value-invoker pair)Acceptance criteria
JavaList<TInterface, TInvoker>(+JavaCollection,JavaDictionaryvariants)JavaPeerContainerFactoryproduces the two-parameter containers (andGetContainerFactory()no longer returnsnullfor interfaces)IList<ISomeListener>) with noMakeGenericType/ reflection on the interfaceContext / references
src/Mono.Android/Java.Interop/JavaPeerProxy.cs,src/Mono.Android/Java.Interop/JavaPeerContainerFactory.cs,src/Mono.Android/Java.Interop/JavaConvert.cs(TryGetFactoryBasedConverter),src/Mono.Android/Android.Runtime/JavaList.cs/JavaCollection.cs/JavaDictionary.cs,src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs(GetContainerFactory).