Investigate eliminating generated class_ref compatibility getters

Open
#12,680 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Refactor
Clarity
Mostly clear
Activity status
Active
Tech stack
csharp

Research direction

Start with the XAJavaInterop1 output in obj/.../mcw/.cs and the approximately 96 non-generated references under src/Mono.Android/**/.cs; generate API 37 bindings and inventory declarations and uses. Trace ownership for generated classes, interface/static-member forms, generic and inherited partials, then validate constructors, fields, nonvirtual calls, legacy assemblies, and listed runtimes. Done means the compatibility and migration behavior is defined, with before/after metadata and size measurements recorded.

Written by the indexing model from the issue text.

Description

Area: Mono.Android
Android framework version

net11.0-android (Preview) / .NET 12 investigation

Affected platform version

dotnet/android main; generated Mono.Android.dll and generated Java binding assemblies.

Description

The XAJavaInterop1 generator emits a compatibility class-handle getter on generated bound classes:

internal static new IntPtr class_ref {
    get { return _members.JniPeerType.PeerReference.Handle; }
}

It also emits the adjacent interface/static-member form:

static IntPtr java_class_ref {
    get { return _members.JniPeerType.PeerReference.Handle; }
}

API 37 generated source currently contains approximately:

  • 5,676 class_ref declarations;
  • 1,102 java_class_ref declarations; and
  • no generated-source references to either name beyond their declarations.

Modern generated method, field, constructor, and registration code accesses JniPeerMembers directly. The generated getters therefore appear to be legacy compatibility shims rather than active generator infrastructure.

Hand-written source under src/Mono.Android/ still contains approximately 96 class_ref references, primarily in older partial implementations for constructors, fields, and explicit nonvirtual calls. Representative examples use it for:

JNIEnv.GetMethodID (class_ref, ...)
JNIEnv.CallNonvirtualVoidMethod (Handle, class_ref, ...)
JNIEnv.StartCreateInstance (class_ref, ...)

These can likely use the owning type's generated _members field directly:

_members.JniPeerType.PeerReference.Handle

or a small shared helper where direct _members access is not available.

The generated getter itself costs a property row, getter MethodDef, method-semantics row, and method body for every emitted type. Across roughly 6,778 declarations, the gross untrimmed framework cost is expected to be around 0.45–0.50 MB, before any secondary cleanup.

This issue should investigate removing these getters from a new/versioned generator format while preserving compatibility for legacy binding source and assemblies.

Compatibility questions:

  • class_ref is internal, but custom binding projects can add partial source to the same generated type and may reference it.
  • Hand-written Mono.Android partials must be migrated before framework generation can omit it.
  • Some generic or inherited partial types may currently rely on a base type's class_ref rather than having direct access to the correct _members field.
  • Static interface fields and alternative/invoker classes may have different ownership requirements for java_class_ref.
  • Old generated binding assemblies already contain the getter and should remain binary-compatible without runtime changes.
  • Regenerating an existing binding project with the new format could break custom partial source even though the member is internal; the format switch or diagnostic needs to make that intentional.

A possible staged approach:

  1. Inventory and migrate all in-tree hand-written class_ref uses to _members.JniPeerType.PeerReference.Handle or an equivalent owner-specific helper.
  2. Verify that generated output has no remaining consumers of class_ref/java_class_ref.
  3. Stop emitting the properties only for a versioned new generator format.
  4. Retain legacy emission for old/default generator modes during transition.
  5. Consider a source diagnostic or documented replacement for custom binding partials that reference class_ref.

Related investigations: #12670 and the ThresholdType/ThresholdClass removal investigation.

Steps to Reproduce
  1. Generate Mono.Android.dll for API 37 using XAJavaInterop1.
  2. Count class_ref and java_class_ref declarations and references in obj/.../mcw/*.cs.
  3. Observe that generated sources declare thousands of getters but do not otherwise reference the names.
  4. Inventory non-generated src/Mono.Android/**/*.cs references.
  5. Replace those references with the owning _members.JniPeerType.PeerReference.Handle and regenerate without compatibility getters.
  6. Build and exercise constructors, static fields/methods, explicit nonvirtual calls, interface invokers, and generic hand-written partials.

The investigation and eventual tests should cover:

  • generated classes and nested classes;
  • abstract classes and interface invokers;
  • static Java fields and methods;
  • constructor activation and explicit nonvirtual calls;
  • hand-written framework partials;
  • generic types and inherited static-member access;
  • interface alternative/static-member classes using java_class_ref;
  • a legacy binding project with custom partial source referencing class_ref;
  • old precompiled binding assemblies; and
  • MonoVM, CoreCLR, NativeAOT, and trimmable-typemap configurations where applicable.

Acceptance criteria for an eventual implementation:

  • New-format generated bindings do not emit unused class_ref/java_class_ref getters.
  • All in-tree hand-written code uses direct JniPeerMembers class-handle access.
  • JNI class ownership and nonvirtual dispatch remain correct.
  • Legacy generator output remains available or migration diagnostics/documentation are provided for custom partial source.
  • Old precompiled binding assemblies continue to run unchanged.
  • Before/after Mono.Android.dll MethodDef, Property, MethodSemantics, IL, metadata, and trimmed-app sizes are recorded.
Did you find any workaround?

No correctness workaround is needed. Current generated getters remain functional, but hand-written code can generally access the same handle through _members.JniPeerType.PeerReference.Handle.

Relevant log output
API 37 generated source:
class_ref declarations:      5,676
class_ref generated uses:    5,676 (declarations only)
java_class_ref declarations: 1,102
java_class_ref generated uses: 1,102 (declarations only)

Non-generated src/Mono.Android source:
class_ref references: approximately 96
Dominant language
C#
Stars
2.1k
Forks
580
Avg merge
2d 19m
Merged PRs (30d)
228

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/android

All issues in dotnet/android

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.