Summary
Suggested by @teo-tsirpanis in dotnet/runtime#126541 (comment)
The source generator currently emits __Attributes(int groupIndex) methods that reconstruct attribute instances in code:
private static Attribute[] __Attributes(int groupIndex)
{
switch (groupIndex)
{
case 0:
return
[
new TestAttribute(),
new CustomAttribute("Foo") { Property = "Bar" },
// ... more attributes
];
}
}
Since MethodInfo.GetCustomAttributes() is AOT-safe (Native AOT's reflection-free mode was deleted in dotnet/runtime#109857), we could replace the generated attribute factories with a reflection call:
// Instead of source-generating attribute construction, delegate to reflection
CreateAttributes = static (groupIndex) => methodInfo.GetCustomAttributes().ToArray(),
Benefits
- Reduced generated code size — eliminates per-class
__Attributes switch methods entirely
- Fewer methods to JIT — one fewer generated method per test class
- Always correct — no risk of source generator failing to replicate complex attribute constructors or property setters
- Simpler generator code — removes
PreGenerateAttributeFactoryBody and related generation logic
Current state
- TUnit's
__Attributes generates only method-level test-relevant attributes (not assembly metadata — the bloated example in the runtime issue was from a different project)
- The reflection path (
ReflectionAttributeExtractor.GetAllAttributes) already uses GetCustomAttributes() and caches results
- Attribute factories are lazy — only invoked during test execution, not discovery — so the reflection cost is deferred
Considerations
- Reflection has per-call overhead vs pre-built arrays, but attribute access is not a hot path (called once per test during setup)
- Would need
MethodInfo available at factory call time — currently the source-gen path avoids storing MethodInfo
- The
[DynamicallyAccessedMembers] annotations would need to cover attribute types
- Could be behind a feature switch if perf characteristics differ between JIT and AOT
Context
Summary
Suggested by @teo-tsirpanis in dotnet/runtime#126541 (comment)
The source generator currently emits
__Attributes(int groupIndex)methods that reconstruct attribute instances in code:Since
MethodInfo.GetCustomAttributes()is AOT-safe (Native AOT's reflection-free mode was deleted in dotnet/runtime#109857), we could replace the generated attribute factories with a reflection call:Benefits
__Attributesswitch methods entirelyPreGenerateAttributeFactoryBodyand related generation logicCurrent state
__Attributesgenerates only method-level test-relevant attributes (not assembly metadata — the bloated example in the runtime issue was from a different project)ReflectionAttributeExtractor.GetAllAttributes) already usesGetCustomAttributes()and caches resultsConsiderations
MethodInfoavailable at factory call time — currently the source-gen path avoids storingMethodInfo[DynamicallyAccessedMembers]annotations would need to cover attribute typesContext