Why are private nested classes included in ref assemblies? #72174
-
I've been working with a customer (@Mike-E-angelo) on a build performance issue he reported. Specifically, this change triggers a cascade of builds that takes a minute to complete: This changes the capture class, which in turn changes the ref assembly, which in turn causes all referencing projects (transitively) to build, which takes a long time. Looking at the reference assemblies shows it's only an unused private nested class that changes: .class public auto ansi sealed beforefieldinit Starbeam.Features.Production.Issuance.Production.LicenseDisplayName
extends class [DragonSpark.Application]DragonSpark.Application.Entities.Queries.Composition.Projection`2<class [Starbeam.Entities]Starbeam.Entities.Marketplace.Publishing.MarketplaceLicense, string>
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
01 00 01 00 00
)
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = (
01 00 03 00 00 00 00 01 01 00 00
)
// Nested Types
- .class nested private auto ansi sealed serializable beforefieldinit '<>c'
+ .class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass4_0'
extends [System.Runtime]System.Object
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Fields
- .field public static initonly class Starbeam.Features.Production.Issuance.Production.LicenseDisplayName/'<>c' '<>9'
- .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
- 01 00 00 00 00
+ .field public class [System.Linq.Expressions]System.Linq.Expressions.Expression`1<class [System.Runtime]System.Func`2<class [Starbeam.Entities]Starbeam.Entities.Marketplace.Publishing.Issuance, string>> name
+ .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8[]) = (
+ 01 00 04 00 00 00 00 01 01 01 00 00
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x4050
// Header size: 1
// Code size: 2 (0x2)
.maxstack 8
// throw null;
IL_0000: ldnull
IL_0001: throw
- } // end of method '<>c'::.ctor
+ } // end of method '<>c__DisplayClass4_0'::.ctor
- } // end of class <>c
+ } // end of class <>c__DisplayClass4_0
// Methods
.method public hidebysig specialname static
class Starbeam.Features.Production.Issuance.Production.LicenseDisplayName get_Default () cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x4050
// Header size: 1
// Code size: 2 (0x2)
.maxstack 8
// throw null;
IL_0000: ldnull
IL_0001: throw
} // end of method LicenseDisplayName::get_Default
.method public hidebysig specialname rtspecialname
instance void .ctor (
class [System.Linq.Expressions]System.Linq.Expressions.Expression`1<class [System.Runtime]System.Func`2<class [Starbeam.Entities]Starbeam.Entities.Marketplace.Publishing.Issuance, string>> name
) cil managed
{
// Method begins at RVA 0x4050
// Header size: 1
// Code size: 2 (0x2)
.maxstack 8
// throw null;
IL_0000: ldnull
IL_0001: throw
} // end of method LicenseDisplayName::.ctor
// Properties
.property class Starbeam.Features.Production.Issuance.Production.LicenseDisplayName Default()
{
.get class Starbeam.Features.Production.Issuance.Production.LicenseDisplayName Starbeam.Features.Production.Issuance.Production.LicenseDisplayName::get_Default()
}
} // end of class Starbeam.Features.Production.Issuance.Production.LicenseDisplayName I was surprised that a private nested class would be included in the ref assembly. Reading refout.md:
I can't see why this private nested class would be needed in the reference assembly, and having it there impacts incremental build performance. Without it, the above change would build in just a few seconds. Could private tested classes be removed from reference assemblies? The future section of the doc suggests there are potential improvements that could be made. Is this one? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 14 replies
-
Pinging @jaredpar @jcouv for insight here. Addressing this would improve incremental build in some scenarios, and potentially make ref assemblies smaller. Is there a reason we couldn't exclude private nested classes? |
Beta Was this translation helpful? Give feedback.
-
The goal of reference assemblies is that they match the behavior of implementation assemblies exactly from the perspective of compilation. In order to safely exclude a type or member from reference assembly we must be certain that it does not impact evaluation in any way. Unfortunately C# is a very complex language and knowing whether a member does or does not impact semantic analysis can be tricky. When we first started implementing reference assemblies we approached it in the manner described in this bug: picking specific combinations of accessibility, nesting, etc ... and excluding it. Every time we did this though, another member of the compiler team was able to come up with an example where that particular exclusion lead to observable differences in compilation. After a few rounds it became clear that we needed a more principled approach. Eventually we settled on a very simple principal for whether or not we should include a type or member in a reference assembly. Essentially
This is a conservative strategy for reference assemblies but it's also one that is very easy to prove to be correct. This does lead to lots of curious entries in reference assemblies. For example you'll see that lambda closure types appear in reference assemblies and it's pretty clear that they don't impact compilation. The general mentality we've had on the compiler team is that we're open to making reference assemblies smaller but there are two criteria:
We are aware that other reference assembly technologies take a different approach here and are more aggressive with trimming types / members. In most cases we've demonstrated that the resulting reference assemblies produces meaningful different results in compilation. A particular sore point is how |
Beta Was this translation helpful? Give feedback.
The goal of reference assemblies is that they match the behavior of implementation assemblies exactly from the perspective of compilation. In order to safely exclude a type or member from reference assembly we must be certain that it does not impact evaluation in any way. Unfortunately C# is a very complex language and knowing whether a member does or does not impact semantic analysis can be tricky.
When we first started implementing reference assemblies we approached it in the manner described in this bug: picking specific combinations of accessibility, nesting, etc ... and excluding it. Every time we did this though, another member of the compiler team was able to come up with an exampl…