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.
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 anew()-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 aTypeLoadExceptionis thrown from insideGetCustomAttributes.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:App.csproj:Publish and run:
Expected behavior
RESULT: OK(as it does untrimmed, and as it does when trimmed on .NET 10).Actual behavior
The constraint violation is bogus:
Repro.Handlerdoes have a public parameterless constructor in source. The trimmer removed it, leaving thenew()constraint unsatisfiable at runtime.Confirmed mechanism
Inspecting the trimmed
App.dllshowsRepro.Handler's parameterless constructor has been removed (the type is kept as an empty shell). Two independent mitigations confirm this is the cause:where T : new()constraint fromMyAttribute<T>→ trimmed app passes.[DynamicDependency(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, typeof(Handler))]from reachable code → ctor preserved, trimmed app passes.Notably, putting
[DynamicallyAccessedMembers(PublicParameterlessConstructor)]on the attribute'sTtype parameter does not preserve the constructor.Regression range
Same source, same
TrimMode=full:Microsoft.NET.ILLink.TasksRESULT: OK10.0.5RESULT: OK11.0.0-preview.4.26230.115TypeLoadExceptionConfiguration
11.0.100-preview.4.26230.115osx-arm64(macOS, Apple Silicon)PublishTrimmed=true,TrimMode=full,SelfContained=trueOther information
This pattern (a
new()-constrained generic attribute resolved via reflection) is used by .NET MAUI'sElementHandlerAttribute<THandler>, where it causedCheckBoxhandler 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.