Skip to content

[net11.0] Workaround #35665: use DI handler registration for CheckBox#35666

Merged
jfversluis merged 1 commit into
net11.0from
dev/simonrozsival/investigate-35665-checkbox-handler
Jun 1, 2026
Merged

[net11.0] Workaround #35665: use DI handler registration for CheckBox#35666
jfversluis merged 1 commit into
net11.0from
dev/simonrozsival/investigate-35665-checkbox-handler

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

Workaround for #35665.

The [ElementHandler<CheckBoxHandler>] attribute on CheckBox causes handler resolution to fail on .NET 11 preview MAUI templates on Android Release builds across all runtime flavors:

  • CoreCLR DefaultSystem.TypeLoadException when the generic attribute is enumerated (GenericArguments[0] ... violates the constraint of type parameter 'THandler').
  • Mono Default / NativeAOTHandlerNotFoundException, since the attribute isn't found and no DI registration exists.

CheckBox was the only type using [ElementHandler<T>]. This PR removes the attribute and restores the regular DI handler registration (AddHandler<CheckBox, CheckBoxHandler>()), which unblocks the failing template builds.

The [ElementHandler<T>] attribute mechanism itself will undergo a major refactoring in #29952, so this is intentionally a minimal, targeted workaround rather than a fix of the attribute path.

Issues Fixed

Fixes #35665

The `[ElementHandler<CheckBoxHandler>]` attribute on CheckBox causes
handler resolution to fail on .NET 11 preview Android Release builds
(TypeLoadException on CoreCLR, HandlerNotFoundException on Mono/NativeAOT).

As a temporary workaround, remove the attribute from CheckBox (the only
type using it) and register the handler via the regular DI path. The
ElementHandler attribute mechanism will be reworked in #29952.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35666

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35666"

@simonrozsival simonrozsival marked this pull request as ready for review May 29, 2026 10:37
@simonrozsival

simonrozsival commented May 29, 2026

Copy link
Copy Markdown
Member Author

Correction + verified root-cause analysis

My earlier comment was wrong on the mechanism and I'm correcting it. Specifically, the claim that "new THandler() has no [DynamicDependency] rooting the constructor" is incorrectnew THandler() under a new() constraint does root the parameterless constructor when that generic instantiation is reachable. That was not the cause.

What is actually verified

  1. CheckBox resolves its handler only through [ElementHandler<CheckBoxHandler>]. MauiHandlersFactory.GetHandler first tries the DI service-type registration, and only if that returns nothing does it fall back to reflecting the attribute (GetCustomAttribute<ElementHandlerAttribute>()CreateHandler()). CheckBox has no AddHandler<> registration, so the attribute path is always taken.

  2. This is not a recent MAUI regression. The entire attribute path (attribute type, its use on CheckBox, and the GetCustomAttribute fallback in MauiHandlersFactory) was introduced together in [net10.0] Trimmable view handlers #28357 (May 2025) and is byte-for-byte identical on net10.0 and net11.0 today. [Android] Implemented material3 support for Label #33599 (Mar 2026) only removed the attribute from Label (moving it back to DI), which left CheckBox as the sole consumer of the attribute path but did not change CheckBox's own behavior. The source-level constraint is fully satisfied (CheckBoxHandler : ICheckBoxHandler : IViewHandler : IElementHandler + public parameterless ctor).

  3. The trigger is the new .NET 11 Android runtime/trimming stacks, not a code change. CoreCLR-on-Android and NativeAOT are new in net11, and Release builds are trimmed. On those stacks, materializing the generic custom attribute ElementHandlerAttribute<CheckBoxHandler> breaks: CoreCLR throws a generic-constraint TypeLoadException while enumerating custom attributes, and Mono/NativeAOT drop the attribute entirely → HandlerNotFoundException. Debug (untrimmed) works. This is a platform-level interaction with a reflection-only generic custom attribute under trimming/AOT; I have not pinned it to a single trimmer/runtime line, and it should be treated as a runtime/trimming issue rather than a MAUI source bug.

Why this workaround is correct regardless of the exact low-level cause

Re-adding handlersCollection.AddHandler<CheckBox, CheckBoxHandler>() makes GetHandler resolve CheckBox via the DI service-type path, which is checked before the attribute. The broken attribute-materialization path is therefore never exercised for CheckBox — sidestepping the runtime/trimmer issue entirely. The attribute mechanism's trim/AOT-safety is the subject of the larger rework in #29952.

cc @ apologies for the earlier inaccurate mechanism description.

@simonrozsival

Copy link
Copy Markdown
Member Author

Root cause confirmed: net11 ILLink trimmer regression → filed dotnet/runtime#128756

I built a minimal standalone reproduction that isolates the crash down to a trimmer bug, independent of MAUI.

What the bug is

The net11 trimmer removes the public parameterless constructor of a type that is used only as the generic argument of a new()-constrained generic custom attribute reached via reflection. The type shell is kept, but its method table is emptied. When the runtime materializes the generic attribute and validates the new() constraint, it's unsatisfiable → TypeLoadException thrown from inside GetCustomAttributes. No IL trim warning is emitted (silent hole).

That's exactly our ElementHandlerAttribute<THandler> where THandler : IElementHandler, new() on CheckBox, resolved reflectively by MauiHandlersFactory.

Minimal repro (no MAUI)

using System;
using Repro;

typeof(CheckBox).GetCustomAttributes(false);
Console.WriteLine("RESULT: OK");

namespace Repro
{
    public class Handler { }
    class MyAttribute<T> : Attribute where T : new() { }
    [My<Handler>] class CheckBox { }
}

<PublishTrimmed>true</PublishTrimmed>, <TrimMode>full</TrimMode>, net11 → crashes with the same stack trace as the issue:

System.TypeLoadException: GenericArguments[0], 'Repro.Handler', on 'Repro.MyAttribute`1[T]' violates the constraint of type parameter 'T'.
   ... at System.Reflection.CustomAttribute.FilterCustomAttributeRecord(...)
   ... at System.RuntimeType.GetCustomAttributes(Boolean inherit)

Confirmed it's a regression (same source, same trim settings)

Target Microsoft.NET.ILLink.Tasks Result
net11.0 untrimmed ✅ OK
net10.0 trimmed (TrimMode=full) 10.0.5 ✅ OK
net11.0 trimmed (TrimMode=full) 11.0.0-preview.4.26230.115 TypeLoadException

Two independent mitigations confirm the mechanism: removing where T : new(), or adding [DynamicDependency(PublicParameterlessConstructor, typeof(Handler))] from reachable code, both fix it. (DAM on the type parameter does not.)

Why this workaround PR is correct

MauiHandlersFactory.GetHandler checks DI service-type registration before the attribute fallback, so re-registering CheckBoxCheckBoxHandler via DI means the broken attribute path is never exercised. This unblocks net11 Release builds today.

Filed upstream

Trimmer bug filed at dotnet/runtime#128756. The full trim/AOT-safe rework of the handler attributes is tracked separately in #29952.

Note: the original issue also reports a HandlerNotFoundException on Mono/NativeAOT. My minimal repro does not reproduce that facet (NativeAOT roots the ctor via the concrete attribute's new T() body), so it's a separate path I'll investigate later — not covered by the runtime issue above.

@jfversluis jfversluis merged commit 05fdfdf into net11.0 Jun 1, 2026
35 of 41 checks passed
@jfversluis jfversluis deleted the dev/simonrozsival/investigate-35665-checkbox-handler branch June 1, 2026 12:04
@github-actions github-actions Bot added this to the .NET 11.0-preview4 milestone Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants