Skip to content

[TrimmableTypeMap] Handle generic invoker type resolution at runtime #11046

Closed as not planned

Description

@simonrozsival

Updated Analysis: Generic Invoker Types Are Not a Real Problem

Original Concern

The legacy GetInvokerType has a MakeGenericType path for generic C# types. The concern was that the trimmable type map would not handle this.

Investigation Result: All Android invokers are non-generic C# types

Java uses type erasure for generics. The Mono.Android binding generator reflects this — all generated interfaces and their invokers are non-generic C# types, even when the Java type has generic parameters:

// java.util.Iterator<E> → non-generic C# interface + non-generic invoker
[Register("java/util/Iterator", "", "Java.Util.IIteratorInvoker")]
[JavaTypeParameters(new string[] {"E"})]  // metadata only, not a C# generic param
public partial interface IIterator : IJavaObject, IJavaPeerable { ... }

[Register("java/util/Iterator", DoNotGenerateAcw=true)]
internal partial class IIteratorInvoker : Java.Lang.Object, IIterator { ... }

The same pattern applies to all 90+ generic Java interfaces:

  • java/util/List → IList + IListInvoker (non-generic)
  • java/util/Map → IMap + IMapInvoker (non-generic)
  • android/os/Parcelable$Creator → IParcelableCreator + IParcelableCreatorInvoker (non-generic)
  • etc.

Runtime Flow (Legacy)

  1. Java returns an object implementing java.util.Iterator
  2. GetJavaToManagedType("java/util/Iterator") → Java.Util.IIterator (non-generic)
  3. Since IIterator is an interface, GetInvokerType(typeof(IIterator)) is called
  4. IIterator has zero generic arguments → takes the simple path: Assembly.GetType("Java.Util.IIteratorInvoker")
  5. IIteratorInvoker is instantiated — no MakeGenericType involved

The MakeGenericType Path Is Dead Code (for Android)

The generic invoker resolution in JavaObjectExtensions.GetInvokerType (lines 137-146) and ManagedTypeManager.GetInvokerTypeCore (lines 48-59) handles C# generics like IFoo<T> → IFooInvoker<T>. But:

  • No generated Mono.Android binding produces generic C# invoker types
  • No Java.Interop or Java.Base types produce generic C# invoker types
  • The code appears to be defensive/future-proofing for a pattern not currently used

Impact on Trimmable Type Map

Low to none. The trimmable path stores InvokerType as a pre-resolved non-generic type reference in the proxy attribute. Since all invokers in Mono.Android are non-generic, this works correctly:

TypeMap entry: "java/util/Iterator" → IIterator_Proxy
IIterator_Proxy.InvokerType → typeof(IIteratorInvoker)  // non-generic, resolves fine

Recommendation

This issue can be deprioritized or closed. The generic invoker scenario is theoretical — it would only matter if:

  1. A third-party binding library creates actual C# generic interfaces (not just [JavaTypeParameters])
  2. Someone uses the Java.Interop [JniTypeSignature] pattern with generic C# types

Neither scenario is common or blocking for .NET 11.

Activity

  1. simonrozsival commented on Mar 30, 2026

    @simonrozsival
    MemberAuthor

    Closing as not planned — this was a false positive.

    Investigation found that generic invoker types don't exist in Mono.Android bindings. Java uses type erasure, and the binding generator reflects this: all 90+ "generic" Java interfaces produce non-generic C# types:

    // java.util.Iterator<E> becomes:
    [Register("java/util/Iterator", "", "Java.Util.IIteratorInvoker")]
    [JavaTypeParameters(new string[] {"E"})]  // metadata only, NOT a C# generic param
    public partial interface IIterator : IJavaObject, IJavaPeerable { }
    
    [Register("java/util/Iterator", DoNotGenerateAcw=true)]
    internal partial class IIteratorInvoker : Java.Lang.Object, IIterator { }  // non-generic

    Same pattern for IList, IMap, IParcelableCreator, IConsumer, IFunction, etc.

    The MakeGenericType path in legacy GetInvokerType / GetInvokerTypeCore is effectively dead code for Android — no generated type in Mono.Android, Java.Interop, or Java.Base produces a generic C# invoker.

    The trimmable type map's pre-resolved non-generic InvokerType TypeRef on the proxy attribute handles this correctly.

  2. locked and limited conversation to collaborators on Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    copilot`copilot-cli` or other AIs were used to author thisneeds-triageIssues that need to be assigned.trimmable-type-map

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions