Repository navigation
Description
The pattern libraries could not be used in an application that is trimmed or compiled ahead of time (NativeAOT). This issue follows #89, which made the aspect framework (PostSharp.dll and the code that the weaver generates) work there.
Measurements before the fix
A net11.0 console application used [NotifyPropertyChanged], [Recordable], [ReaderWriterSynchronized], [Cache] with MemoryCachingBackend, and [Log] with ConsoleLoggingBackend. It was published for win-x64 with the .NET 11 RC1 SDK and the packages of the branch of #89. Under the JIT, every aspect worked.
With NativeAOT:
| Aspect | Result |
|---|---|
[Log] |
The module initializer failed: MissingMethodException, no parameterless constructor for QueryableFormatter, which FormatterRepository.Register creates by reflection. The application could not start. |
[NotifyPropertyChanged], [Recordable], [ReaderWriterSynchronized] |
The initializer of the aspects failed: PortableSerializationException, no serializer for WeakEventHandler<...>, which relied on the nested Serializer naming convention. |
[Cache] |
Failed with ArgumentException: EnumerableAdapter<T> "has to inherit from IValueAdapter<T>", because the trimmer removed the interfaces of the generic definition. |
ILC reported 39 warnings in the Patterns assemblies. With IsAotCompatible, the analyzers reported 107 in the net10.0 builds of Common, Aggregation, Model, Threading, Diagnostics and Caching; most of them in code that only the PostSharp compiler runs.
Fix
- The
net10.0builds of Common, Aggregation, Model, Threading, Diagnostics and Caching setIsAotCompatibleand build without any trimming or AOT warning. - Compile-time code is behind a feature switch.
CompileTimeValidate,CompileTimeInitialize, the selectors of the pointcuts, the dependency analysis of[NotifyPropertyChanged],ThreadSafetyPolicyand the other code that only the compiler runs test the internal switchPostSharp.Patterns.IsCompileTimeCodeSupported, or require unreferenced code. PostSharp.Patterns.Common.Redist sets the switch tofalse, withTrim="true", in an application that is trimmed or compiled ahead of time, so the trimmer removes that code. - The advice factories of the advisable collections are created without reflection.
AdvisableCollection<T>,AdvisableDictionary<TKey, TValue>andAdvisableHashSet<T>pass their generic arguments to a factory provider of each library, so that[Aggregatable],[Disposable],[Recordable],[Freezable]and the threading models no longer callMakeGenericTypeandActivator.CreateInstance. - Formatters and value adapters keep their constructors and interfaces.
FormatterRepository<TRole>.Register(Type, Type),ValueAdapterFactory.Register(Type, Type)and the attributes that register type adapters and field rules annotate the registered type withDynamicallyAccessedMembers. Under NativeAOT, a generic formatter or value adapter over reference types is then created by reflection; one over a value type has native code only when the application instantiates it (measured). In that case a formatter is skipped for that type in favor of the next one (a non-generic collection formatter is registered under NativeAOT, so that aList<int>still gets a cache key that depends on its items), and a value adapter throws aNotSupportedExceptionthat names the type, because caching the value without its adapter would be wrong. WeakEventHandlernames its serializer with[Serializer]. In PostSharp.dll,ReflectionSerializerFactorynow creates a generic serializer under NativeAOT instead of refusing it, which works for reference-type arguments.- The custom logging API annotates
LogEventData.Create<T>andUnknownObjectAccessor.GetFactory<T>so that the properties ofTare kept, and marks the overloads that take anobjectwithRequiresUnreferencedCode. Logging an augmented event no longer compiles an expression for each type. - Under NativeAOT, two analyses that read metadata at run time are made conservative, because NativeAOT has no metadata for the fields or methods of a type that is not reflected on:
- The type analysis of Aggregation classifies a type without an aspect as mutable. Before, it saw no field and classified it as immutable, so that
[Freezable]did not advise a childAdvisableCollection<int>, and the collection of a frozen object stayed writable (measured). DefaultFormatteralways callsToString, and formats the name of the type whenToStringreturns it. Otherwise it could miss an override and format every value of a type as the name of the type, so that two values would get the same cache key.
- The type analysis of Aggregation classifies a type without an aspect as mutable. Before, it saw no field and classified it as immutable, so that
- A static handler of a weak event (
[WeakEvent], and the events of[NotifyPropertyChanged]) was kept through a weak reference to itsMethodInfo. NativeAOT creates a newMethodInfofor each access, so the handler was collected (measured). The weak reference is now to the invocation delegate, which the event holds. DefaultFormatterformats a nullable value whenNullableFormatter<,>has no native code for its type. A null value threw aNullReferenceException(measured).- Other reflection over the types of the application is kept and documented: the properties of an object passed to the custom logging API as an
objectare not logged when trimming removes them, and the designer detection of Windows Forms and WPF fails silently, as these frameworks do not support trimming.
Tests
Tests/Core/Issues/IssueGH252publishes an application that uses[NotifyPropertyChanged],[Recordable],[ReaderWriterSynchronized],[Freezable],[Disposable],[Cache]and[Log], with advisable collections, dictionaries and hash sets of value types, without trimming, trimmed, and with NativeAOT. It runs each program and fails on anywarning ILin the publication output.- The unit tests of Common, Aggregation, Model, Threading, Diagnostics and Caching pass under the JIT on
net10.0andnet472. -p:PostSharpTestNativeAot=truepublishes a Patterns unit test application with NativeAOT, with the.aotpackages of xunit.v3 (Documentation/Testing.md). Measured onwin-x64, .NET 10: Common 213/217, Aggregation 228/252, Model 576/599, Threading 544/577 (12 skipped), Diagnostics (console backend) 247/253 (6 skipped, no failure). The two defects above (weak static handlers, null nullable values) were found this way. The remaining failures are expected: tests ofBinaryFormatter,DataContractSerializerandXmlSerializer, test helpers that use reflection, the formatting of anonymous types and of objects passed asobjectto the custom logging API, and the type analysis of Aggregation, which is conservative under NativeAOT. It is not a CI cell yet.
Not covered
- Generic adapters and formatters over a value type that the application registers at run time have no native code under NativeAOT. Those of the libraries are requested by the aspects since Let aspects request the native code of generic instances for NativeAOT #254, so
[Cache]on a method that returnsIEnumerable<int>works. MemoryCachingBackendusesSystem.Runtime.Caching, which reads the configuration of the application throughSystem.Configuration, which trimming breaks: a trimmed application fails when it creates the backend.MemoryCacheBackend(PostSharp.Patterns.Caching.IMemoryCache) works.JsonCachingSerializerandBinarySerializerrequire unreferenced and dynamic code. AJsonCachingSerializerwith a source-generatedJsonSerializerContextworks, but its constructor still reports the warning.- The backends and adapters of the logging and caching libraries,
PostSharp.Patterns.Diagnostics.Configuration,PostSharp.Patterns.Xamland the Windows-specific parts ofPostSharp.Patterns.Threadingare not annotated. - Anonymous types: their properties are not formatted or logged under NativeAOT, because they have no metadata.
- The type analysis of Aggregation classifies a type without an aspect as mutable under NativeAOT, so an immutable type that is not annotated with
[Immutable]or[AssumeImmutable]is treated as mutable at run time. Inferred from the code, not measured: such an object, added at run time to a collection of a frozen or immutable object, is rejected where the JIT accepts it. - NativeAOT was measured on
win-x64only.
— Claude for Gael
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
gfraiteur commentedon Oct 14, 2026
Released in PostSharp 2027.0.4-preview.
-- Claude for Gael