Skip to content

Trimmer removes parameterless constructor of a type used only as a new()-constrained generic attribute argument, causing TypeLoadException at GetCustomAttributes (net11 regression) #128756

@simonrozsival

Description

@simonrozsival

Description

The .NET 11 trimmer (Microsoft.NET.ILLink.Tasks) removes the public parameterless constructor of a type that is used only as the generic argument of a new()-constrained generic custom attribute, when that attribute is reached only via reflection (Type.GetCustomAttributes).

The type's shell is kept (its name is referenced from attribute metadata), but its method table is emptied. When the runtime later materializes the generic attribute and validates the new() constraint, the constraint is unsatisfiable and a TypeLoadException is thrown from inside GetCustomAttributes.

This is a regression in .NET 11: the exact same source and trim settings work correctly on .NET 10.

No IL trim warning is emitted — this is a silent hole.

Minimal reproduction

Program.cs:

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 { }
}

App.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>full</TrimMode>
    <SelfContained>true</SelfContained>
    <RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
  </PropertyGroup>
</Project>

Publish and run:

dotnet publish -c Release -r osx-arm64 -o ./bin/pub
./bin/pub/App

Expected behavior

RESULT: OK (as it does untrimmed, and as it does when trimmed on .NET 10).

Actual behavior

Unhandled exception. System.TypeLoadException: GenericArguments[0], 'Repro.Handler', on 'Repro.MyAttribute`1[T]' violates the constraint of type parameter 'T'.
   at System.ModuleHandle.ResolveType(...)
   at System.ModuleHandle.ResolveTypeHandle(...)
   at System.Reflection.RuntimeModule.ResolveType(...)
   at System.Reflection.CustomAttribute.FilterCustomAttributeRecord(...)
   at System.Reflection.CustomAttribute.AddCustomAttributes(...)
   at System.Reflection.CustomAttribute.GetCustomAttributes(...)
   at System.RuntimeType.GetCustomAttributes(Boolean inherit)
   at Program.<Main>$(String[] args) in Program.cs:line 4

The constraint violation is bogus: Repro.Handler does have a public parameterless constructor in source. The trimmer removed it, leaving the new() constraint unsatisfiable at runtime.

Confirmed mechanism

Inspecting the trimmed App.dll shows Repro.Handler's parameterless constructor has been removed (the type is kept as an empty shell). Two independent mitigations confirm this is the cause:

  • Removing the where T : new() constraint from MyAttribute<T> → trimmed app passes.
  • Adding [DynamicDependency(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, typeof(Handler))] from reachable code → ctor preserved, trimmed app passes.

Notably, putting [DynamicallyAccessedMembers(PublicParameterlessConstructor)] on the attribute's T type parameter does not preserve the constructor.

Regression range

Same source, same TrimMode=full:

Target Microsoft.NET.ILLink.Tasks Result
net11.0 (untrimmed) RESULT: OK
net10.0 trimmed 10.0.5 RESULT: OK
net11.0 trimmed 11.0.0-preview.4.26230.115 TypeLoadException

Configuration

  • SDK: 11.0.100-preview.4.26230.115
  • RID: osx-arm64 (macOS, Apple Silicon)
  • PublishTrimmed=true, TrimMode=full, SelfContained=true

Other information

This pattern (a new()-constrained generic attribute resolved via reflection) is used by .NET MAUI's ElementHandlerAttribute<THandler>, where it caused CheckBox handler resolution to crash at startup in net11 Release builds — see dotnet/maui#35665. MAUI has a workaround, but the underlying trimmer behavior looks like a bug.

Metadata

Metadata

Labels

area-Tools-ILLink.NET linker development as well as trimming analyzers

Type

No type
No fields configured for issues without a type.

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions