Skip to content

Commit aa0f6d9

Browse files
simonrozsivalCopilotjonathanpeppers
authored
[TrimmableTypeMap] Implement runtime TypeManager, ValueManager, and JavaConvert (#10967)
Fixes: #10791 ## Summary Adds the runtime-side support for the trimmable typemap: type resolution, peer creation, native method registration, and AOT-safe collection marshaling. All behind `RuntimeFeature.TrimmableTypeMap` (defaults to `false`). ### New runtime types - **`JavaPeerProxy` / `JavaPeerProxy<T>`** — AOT-safe proxy attribute base. Generated proxy types extend this and provide `CreateInstance()` for peer creation and `GetContainerFactory()` for collection marshaling. - **`IAndroidCallableWrapper`** — `RegisterNatives(JniType)` interface for ACW proxy types to register JNI native methods. - **`JavaPeerContainerFactory`** — AOT-safe factories for arrays, lists, collections, and dictionaries without `MakeGenericType()`. - **`TrimmableTypeMap`** — Central class owning the `TypeMapping` dictionary. Encapsulates all proxy attribute access: peer creation, invoker resolution, container factories, and native method registration bootstrap. - **`TrimmableTypeMapTypeManager`** — `JniTypeManager` subclass delegating type lookups to `TrimmableTypeMap`. `RegisterNativeMembers` throws `UnreachableException` (JCW static blocks handle registration). ### Type resolution - `GetProxyForManagedType(Type)` resolves managed type → JNI name via `IJniNameProviderAttribute` (shared interface for `[Register]` and `[JniTypeSignature]`) → TypeMap dictionary → proxy. Results cached in `ConcurrentDictionary`. - `TryGetJniNameForType` shared between `TrimmableTypeMap` and `TrimmableTypeMapTypeManager`. - `ActivateInstance` uses JNI `GetObjectClass` → `GetJniTypeNameFromClass` → TypeMap lookup for constructor activation (proxy types have self-applied attribute, not the target type). ### TypeMap dictionary initialization - The runtime's `GetOrCreateExternalTypeMapping<Java.Lang.Object>()` handles assembly resolution automatically via the `System.Runtime.InteropServices.TypeMappingEntryAssembly` runtimeconfig property (set in `Trimmable.targets`). No manual `Assembly.Load` pre-loading is needed — the runtime walks `TypeMapAssemblyTargetAttribute` attributes from the entry assembly recursively. ### Native interop refactoring - **`RegisterJniNatives` passed via init args** — `Initialize` sets `args->registerJniNativesFn` (null when `TrimmableTypeMap=true`). Eliminates the `create_delegate` call for `RegisterJniNatives`, letting the trimmer remove it cleanly in the trimmable path. - **C++ `registerNatives` stub** — `Host::Java_mono_android_Runtime_registerNatives` no-op for the trimmable path (managed code handles registration via `Initialize`). - **Null guard** on `jnienv_register_jni_natives` call site. - `Initialize` is now the **single** `create_delegate` entry point from native code. ### RegisterNatives bootstrap - `mono.android.Runtime.registerNatives(Class)` Java native method added - `TrimmableTypeMap.Initialize()` registers the JNI callback during init (behind explicit `if (RuntimeFeature.TrimmableTypeMap)` guard for trimmer compatibility) - When Java loads a JCW class, `OnRegisterNatives` resolves the proxy and calls `IAndroidCallableWrapper.RegisterNatives()` to bind UCO function pointers ### AOT-safe JavaConvert - `JavaConvert.GetJniHandleConverter()` uses `JavaPeerContainerFactory` for `IList`, `ICollection`, `IDictionary` - `JNIEnv.ArrayCreateInstance()` uses factory path ### Peer creation - `JavaMarshalValueManager` (renamed from `ManagedValueManager`) overrides `CreatePeer` for the trimmable path — calls `proxy.CreateInstance()` directly, bypassing `GetUninitializedObject` - `GetSimpleReferences` walks base type chain for managed-only subclasses without `[Register]` ### Wiring - `RuntimeFeature.TrimmableTypeMap` feature switch with ILLink substitutions - `JNIEnvInit` (CoreCLR) and `JavaInteropRuntime` + `JreRuntime` (NativeAOT) create the new managers when the feature is on - `JavaMarshalValueManager` (renamed from `ManagedValueManager`) gets proxy-based peer creation in `TryConstructPeer` ### Dependencies - dotnet/java-interop#1391 (RegisterNatives with raw function pointers — follow-up optimization) - Manifest generator: #10991 - Build pipeline: #10980 ### Test coverage - 255/255 generator tests pass - End-to-end HelloWorld app runs on device with Activity, layout XML, FindViewById\<Button\>, Click events - Runtime tests require device integration (tracked by #10793) ### Trimmer size regression prevention - Add ILLink substitutions for `IsMonoRuntime` and `IsCoreClrRuntime` — enables trimmer to eliminate runtime-specific branches - `JavaMarshalValueManager`: remove `TrimmableTypeMap?` field, use `TrimmableTypeMap.Instance` singleton - `TrimmableTypeMap`: private ctor, `Initialize()` static method, `Instance` throws if not initialized - `TrimmableTypeMapTypeManager`: no constructor parameter, uses singleton - No `TrimmableTypeMap` type references outside feature guards — clean trimming for MonoVM/NativeAOT Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
1 parent 7f6ed40 commit aa0f6d9

File tree

19 files changed

+695
-57
lines changed

19 files changed

+695
-57
lines changed

src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,13 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua
6161
var settings = new DiagnosticSettings ();
6262
settings.AddDebugDotnetLog ();
6363

64+
var typeManager = CreateTypeManager ();
65+
6466
var options = new NativeAotRuntimeOptions {
6567
EnvironmentPointer = jnienv,
6668
ClassLoader = new JniObjectReference (classLoader, JniObjectReferenceType.Global),
67-
TypeManager = new ManagedTypeManager (),
68-
ValueManager = ManagedValueManager.GetOrCreateInstance (),
69+
TypeManager = typeManager,
70+
ValueManager = new JavaMarshalValueManager (),
6971
UseMarshalMemberBuilder = false,
7072
JniGlobalReferenceLogWriter = settings.GrefLog,
7173
JniLocalReferenceLogWriter = settings.LrefLog,
@@ -86,4 +88,13 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua
8688
}
8789
transition.Dispose ();
8890
}
91+
92+
static JniRuntime.JniTypeManager CreateTypeManager ()
93+
{
94+
if (RuntimeFeature.TrimmableTypeMap) {
95+
return new TrimmableTypeMapTypeManager ();
96+
}
97+
98+
return new ManagedTypeManager ();
99+
}
89100
}

src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ static NativeAotRuntimeOptions CreateJreVM (NativeAotRuntimeOptions builder)
5858
throw new InvalidOperationException ($"Member `{nameof (NativeAotRuntimeOptions)}.{nameof (NativeAotRuntimeOptions.JvmLibraryPath)}` must be set.");
5959

6060
#if NET
61-
builder.TypeManager ??= new ManagedTypeManager ();
61+
builder.TypeManager ??= CreateDefaultTypeManager ();
6262
#endif // NET
6363

64-
builder.ValueManager ??= ManagedValueManager.GetOrCreateInstance();
64+
builder.ValueManager ??= JavaMarshalValueManager.Instance;
6565
builder.ObjectReferenceManager ??= new Android.Runtime.AndroidObjectReferenceManager ();
6666

6767
if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero)
@@ -75,6 +75,15 @@ internal protected JreRuntime (NativeAotRuntimeOptions builder)
7575
{
7676
}
7777

78+
static JniRuntime.JniTypeManager CreateDefaultTypeManager ()
79+
{
80+
if (RuntimeFeature.TrimmableTypeMap) {
81+
return new TrimmableTypeMapTypeManager ();
82+
}
83+
84+
return new ManagedTypeManager ();
85+
}
86+
7887
public override string? GetCurrentManagedThreadName ()
7988
{
8089
return Thread.CurrentThread.Name;

src/Mono.Android/Android.Runtime/JNIEnv.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,18 @@ public static partial class JNIEnv {
2424

2525
public static IntPtr Handle => JniEnvironment.EnvironmentPointer;
2626

27-
static Array ArrayCreateInstance (Type elementType, int length) =>
28-
// FIXME: https://github.com/xamarin/xamarin-android/issues/8724
29-
// IL3050 disabled in source: if someone uses NativeAOT, they will get the warning.
30-
#pragma warning disable IL3050
31-
Array.CreateInstance (elementType, length);
27+
static Array ArrayCreateInstance (Type elementType, int length)
28+
{
29+
if (RuntimeFeature.TrimmableTypeMap) {
30+
var factory = TrimmableTypeMap.Instance?.GetContainerFactory (elementType);
31+
if (factory is not null)
32+
return factory.CreateArray (length, 1);
33+
}
34+
35+
#pragma warning disable IL3050 // Array.CreateInstance is not AOT-safe, but this is the legacy fallback path
36+
return Array.CreateInstance (elementType, length);
3237
#pragma warning restore IL3050
38+
}
3339

3440
static Type MakeArrayType (Type type) =>
3541
// FIXME: https://github.com/xamarin/xamarin-android/issues/8724

src/Mono.Android/Android.Runtime/JNIEnvInit.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ internal struct JnienvInitializeArgs {
3636
public IntPtr grefGCUserPeerable;
3737
public bool managedMarshalMethodsLookupEnabled;
3838
public IntPtr propagateUncaughtExceptionFn;
39+
public IntPtr registerJniNativesFn;
3940
}
4041
#pragma warning restore 0649
4142

@@ -81,7 +82,10 @@ static Type TypeGetType (string typeName) =>
8182
JniType.GetCachedJniType (ref jniType, className);
8283

8384
ReadOnlySpan<char> methods = new ReadOnlySpan<char> ((void*) methods_ptr, methods_len);
84-
androidRuntime!.TypeManager.RegisterNativeMembers (jniType, type, methods);
85+
if (androidRuntime is null) {
86+
throw new InvalidOperationException ("androidRuntime has not been initialized");
87+
}
88+
androidRuntime.TypeManager.RegisterNativeMembers (jniType, type, methods);
8589
}
8690

8791
// This must be called by NativeAOT before InitializeJniRuntime, as early as possible
@@ -130,16 +134,20 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
130134

131135
BoundExceptionType = (BoundExceptionType)args->ioExceptionType;
132136
JniRuntime.JniTypeManager typeManager;
133-
JniRuntime.JniValueManager valueManager;
134-
if (RuntimeFeature.ManagedTypeMap) {
137+
JniRuntime.JniValueManager? valueManager = null;
138+
if (RuntimeFeature.TrimmableTypeMap) {
139+
typeManager = new TrimmableTypeMapTypeManager ();
140+
valueManager = new JavaMarshalValueManager ();
141+
} else if (RuntimeFeature.ManagedTypeMap) {
135142
typeManager = new ManagedTypeManager ();
136143
} else {
137144
typeManager = new AndroidTypeManager (args->jniAddNativeMethodRegistrationAttributePresent != 0);
138145
}
139146
if (RuntimeFeature.IsMonoRuntime) {
140147
valueManager = new AndroidValueManager ();
141148
} else if (RuntimeFeature.IsCoreClrRuntime) {
142-
valueManager = ManagedValueManager.GetOrCreateInstance ();
149+
// Note: this will be removed once trimmable typemap is the only supported option for CoreCLR runtime
150+
valueManager ??= new JavaMarshalValueManager ();
143151
} else {
144152
throw new NotSupportedException ("Internal error: unknown runtime not supported");
145153
}
@@ -152,6 +160,10 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
152160
args->jniAddNativeMethodRegistrationAttributePresent != 0
153161
);
154162

163+
if (RuntimeFeature.TrimmableTypeMap) {
164+
TrimmableTypeMap.Initialize ();
165+
}
166+
155167
grefIGCUserPeer_class = args->grefIGCUserPeer;
156168
grefGCUserPeerable_class = args->grefGCUserPeerable;
157169

@@ -165,6 +177,10 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
165177
}
166178

167179
args->propagateUncaughtExceptionFn = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr, IntPtr, void>)&PropagateUncaughtException;
180+
181+
if (!RuntimeFeature.TrimmableTypeMap) {
182+
args->registerJniNativesFn = (IntPtr)(delegate* unmanaged<IntPtr, int, IntPtr, IntPtr, int, void>)&RegisterJniNatives;
183+
}
168184
RunStartupHooksIfNeeded ();
169185
SetSynchronizationContext ();
170186
}

src/Mono.Android/ILLink/ILLink.Substitutions.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,5 @@
44
<method signature="System.Boolean get_NegotiateAuthenticationIsEnabled()" body="stub" feature="Xamarin.Android.Net.UseNegotiateAuthentication" featurevalue="false" value="false" />
55
<method signature="System.Boolean get_NegotiateAuthenticationIsEnabled()" body="stub" feature="Xamarin.Android.Net.UseNegotiateAuthentication" featurevalue="true" value="true" />
66
</type>
7-
<type fullname="Microsoft.Android.Runtime.RuntimeFeature">
8-
<method signature="System.Boolean get_ManagedTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.ManagedTypeMap" featurevalue="false" value="false" />
9-
<method signature="System.Boolean get_ManagedTypeMap()" body="stub" feature="Microsoft.Android.Runtime.RuntimeFeature.ManagedTypeMap" featurevalue="true" value="true" />
10-
</type>
117
</assembly>
128
</linker>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#nullable enable
2+
3+
using System;
4+
5+
namespace Java.Interop
6+
{
7+
/// <summary>
8+
/// Interface for proxy types that represent Android Callable Wrappers (ACW).
9+
/// ACW types are .NET types that have a corresponding generated Java class
10+
/// which calls back into .NET via JNI native methods.
11+
/// </summary>
12+
public interface IAndroidCallableWrapper
13+
{
14+
/// <summary>
15+
/// Registers JNI native methods for this ACW type.
16+
/// Called when the Java class is first loaded and needs its native methods bound.
17+
/// </summary>
18+
/// <param name="nativeClass">The JNI type for the Java class.</param>
19+
void RegisterNatives (JniType nativeClass);
20+
}
21+
}

src/Mono.Android/Java.Interop/JavaConvert.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Reflection;
77

88
using Android.Runtime;
9+
using Microsoft.Android.Runtime;
910

1011
namespace Java.Interop {
1112

@@ -79,6 +80,13 @@ params Type [] typeArguments
7980
return converter;
8081
if (target.IsArray)
8182
return (h, t) => JNIEnv.GetArray (h, t, target.GetElementType ());
83+
84+
if (RuntimeFeature.TrimmableTypeMap) {
85+
var factoryConverter = TryGetFactoryBasedConverter (target);
86+
if (factoryConverter != null)
87+
return factoryConverter;
88+
}
89+
8290
if (target.IsGenericType && target.GetGenericTypeDefinition() == typeof (IDictionary<,>)) {
8391
Type t = MakeGenericType (typeof (JavaDictionary<,>), target.GetGenericArguments ());
8492
return GetJniHandleConverterForType (t);
@@ -101,6 +109,52 @@ params Type [] typeArguments
101109
return null;
102110
}
103111

112+
/// <summary>
113+
/// AOT-safe converter using <see cref="JavaPeerContainerFactory"/> from the generated proxy.
114+
/// Avoids <c>MakeGenericType()</c> by using the pre-typed factory from the proxy attribute.
115+
/// </summary>
116+
static Func<IntPtr, JniHandleOwnership, object?>? TryGetFactoryBasedConverter (Type target)
117+
{
118+
if (!target.IsGenericType)
119+
return null;
120+
121+
var genericDef = target.GetGenericTypeDefinition ();
122+
var typeArgs = target.GetGenericArguments ();
123+
124+
if (genericDef == typeof (IList<>) && typeArgs.Length == 1) {
125+
var factory = TryGetContainerFactory (typeArgs [0]);
126+
if (factory != null)
127+
return (h, t) => factory.CreateList (h, t);
128+
}
129+
130+
if (genericDef == typeof (ICollection<>) && typeArgs.Length == 1) {
131+
var factory = TryGetContainerFactory (typeArgs [0]);
132+
if (factory != null)
133+
return (h, t) => factory.CreateCollection (h, t);
134+
}
135+
136+
if (genericDef == typeof (IDictionary<,>) && typeArgs.Length == 2) {
137+
var keyFactory = TryGetContainerFactory (typeArgs [0]);
138+
var valueFactory = TryGetContainerFactory (typeArgs [1]);
139+
if (keyFactory != null && valueFactory != null)
140+
return (h, t) => valueFactory.CreateDictionary (keyFactory, h, t);
141+
}
142+
143+
return null;
144+
}
145+
146+
static JavaPeerContainerFactory? TryGetContainerFactory (Type elementType)
147+
{
148+
if (!typeof (IJavaPeerable).IsAssignableFrom (elementType))
149+
return null;
150+
151+
if (RuntimeFeature.TrimmableTypeMap) {
152+
return TrimmableTypeMap.Instance?.GetContainerFactory (elementType);
153+
}
154+
155+
return null;
156+
}
157+
104158
static Func<IntPtr, JniHandleOwnership, object> GetJniHandleConverterForType ([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t)
105159
{
106160
MethodInfo m = t.GetMethod ("FromJniHandle", BindingFlags.Static | BindingFlags.Public)!;
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#nullable enable
2+
3+
using System;
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Runtime.CompilerServices;
8+
using Android.Runtime;
9+
10+
namespace Java.Interop
11+
{
12+
/// <summary>
13+
/// AOT-safe factory for creating typed containers (arrays, lists, collections, dictionaries)
14+
/// without using <c>MakeGenericType()</c> or <c>Array.CreateInstance()</c>.
15+
/// </summary>
16+
public abstract class JavaPeerContainerFactory
17+
{
18+
/// <summary>
19+
/// Creates a typed jagged array. Rank 1 = T[], rank 2 = T[][], etc.
20+
/// </summary>
21+
internal abstract Array CreateArray (int length, int rank);
22+
23+
/// <summary>
24+
/// Creates a typed <c>JavaList&lt;T&gt;</c> from a JNI handle.
25+
/// </summary>
26+
internal abstract IList CreateList (IntPtr handle, JniHandleOwnership transfer);
27+
28+
/// <summary>
29+
/// Creates a typed <c>JavaCollection&lt;T&gt;</c> from a JNI handle.
30+
/// </summary>
31+
internal abstract ICollection CreateCollection (IntPtr handle, JniHandleOwnership transfer);
32+
33+
/// <summary>
34+
/// Creates a typed <c>JavaDictionary&lt;TKey, TValue&gt;</c> using the visitor pattern.
35+
/// This factory provides the value type; <paramref name="keyFactory"/> provides the key type.
36+
/// </summary>
37+
internal virtual IDictionary? CreateDictionary (JavaPeerContainerFactory keyFactory, IntPtr handle, JniHandleOwnership transfer)
38+
=> null;
39+
40+
/// <summary>
41+
/// Visitor callback invoked by the value factory's <see cref="CreateDictionary"/>.
42+
/// Override in <see cref="JavaPeerContainerFactory{T}"/> to provide both type parameters.
43+
/// </summary>
44+
internal virtual IDictionary? CreateDictionaryWithValueFactory<TValue> (
45+
JavaPeerContainerFactory<TValue> valueFactory, IntPtr handle, JniHandleOwnership transfer)
46+
where TValue : class, IJavaPeerable
47+
=> null;
48+
49+
/// <summary>
50+
/// Creates a <see cref="JavaPeerContainerFactory{T}"/> singleton for the specified type.
51+
/// </summary>
52+
public static JavaPeerContainerFactory Create<
53+
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
54+
T
55+
> () where T : class, IJavaPeerable
56+
=> JavaPeerContainerFactory<T>.Instance;
57+
}
58+
59+
/// <summary>
60+
/// Typed container factory. All creation uses direct <c>new</c> expressions — fully AOT-safe.
61+
/// </summary>
62+
/// <typeparam name="T">The Java peer element type.</typeparam>
63+
public sealed class JavaPeerContainerFactory<
64+
// TODO (https://github.com/dotnet/android/issues/10794): Remove this DAM annotation — it preserves too much reflection metadata on all types in the typemap.
65+
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
66+
T
67+
> : JavaPeerContainerFactory
68+
where T : class, IJavaPeerable
69+
{
70+
internal static readonly JavaPeerContainerFactory<T> Instance = new ();
71+
72+
JavaPeerContainerFactory () { }
73+
74+
// TODO (https://github.com/dotnet/android/issues/10794): This might cause unnecessary code bloat for NativeAOT. Revisit
75+
// how we use this API and instead use a differnet approach that uses AOT-safe `Array.CreateInstanceFromArrayType`
76+
// with statically provided array types based on a statically known array type.
77+
internal override Array CreateArray (int length, int rank) => rank switch {
78+
1 => new T [length],
79+
2 => new T [length][],
80+
3 => new T [length][][],
81+
_ when rank >= 0 => CreateHigherRankArray (length, rank),
82+
_ => throw new ArgumentOutOfRangeException (nameof (rank), rank, "Rank must be non-negative."),
83+
};
84+
85+
static Array CreateHigherRankArray (int length, int rank)
86+
{
87+
if (!RuntimeFeature.IsDynamicCodeSupported) {
88+
throw new NotSupportedException ($"Cannot create array of rank {rank} because dynamic code is not supported.");
89+
}
90+
91+
var arrayType = typeof (T);
92+
for (int i = 0; i < rank; i++) {
93+
arrayType = arrayType.MakeArrayType ();
94+
}
95+
96+
return Array.CreateInstanceFromArrayType (arrayType, length);
97+
}
98+
99+
internal override IList CreateList (IntPtr handle, JniHandleOwnership transfer)
100+
=> new Android.Runtime.JavaList<T> (handle, transfer);
101+
102+
internal override ICollection CreateCollection (IntPtr handle, JniHandleOwnership transfer)
103+
=> new Android.Runtime.JavaCollection<T> (handle, transfer);
104+
105+
internal override IDictionary? CreateDictionary (JavaPeerContainerFactory keyFactory, IntPtr handle, JniHandleOwnership transfer)
106+
=> keyFactory.CreateDictionaryWithValueFactory (this, handle, transfer);
107+
108+
#pragma warning disable IL2091 // DynamicallyAccessedMembers on base method type parameter cannot be repeated on override in C#
109+
internal override IDictionary? CreateDictionaryWithValueFactory<TValue> (
110+
JavaPeerContainerFactory<TValue> valueFactory, IntPtr handle, JniHandleOwnership transfer)
111+
=> new Android.Runtime.JavaDictionary<T, TValue> (handle, transfer);
112+
#pragma warning restore IL2091
113+
}
114+
}

0 commit comments

Comments
 (0)