@@ -187,22 +187,35 @@ internal static bool TryCreate(ModelInspector parent, Type type, [NotNullWhen(tr
187187 // This list is created lazily. This not only improves initial startup time of
188188 // applications but also ensures circular references between types will not cause loops.
189189 private PropertyMappingCollection ? _mappings ;
190-
191- private PropertyMappingCollection PropertyMappingsInternal
190+ private object ? _mappingsLock ;
191+
192+ // Note on the `_field ?? LazyInitializer.EnsureInitialized(...)` shape of this member, the
193+ // other lazily initialized members below and the equivalents in PropertyMapping and
194+ // PropertyMappingCollection: calling EnsureInitialized unconditionally would allocate its
195+ // factory delegate on *every* access, including the warm path where the field has long been
196+ // initialized, and these members are read per element on the (de)serialization path. The
197+ // null-coalescing read short-circuits that: once the field is initialized, an access is an
198+ // ordinary read that allocates nothing, and the factory delegate is only created on the
199+ // once-only cold path. The ordinary read is enough because the runtime guarantees that
200+ // object reference stores are release stores and that data-dependent reads are ordered, so a
201+ // thread observing the field non-null also observes the fully initialized object; see
202+ // https://github.com/dotnet/runtime/blob/main/docs/design/specs/Memory-model.md.
203+ //
204+ // This member uses the EnsureInitialized overload with a syncLock, so that the (possibly
205+ // user-supplied) property mapper building this mutable collection runs exactly once. The other
206+ // lazy members have idempotent factories whose results are never mutated afterwards, so they
207+ // use the lock-free overload, where racing factories may run concurrently but only a single
208+ // result is ever published and handed out.
209+ private PropertyMappingCollection PropertyMappingsInternal =>
210+ _mappings ?? LazyInitializer . EnsureInitialized ( ref _mappings , ref _mappingsLock , createPropertyMappings ) ! ;
211+
212+ private PropertyMappingCollection createPropertyMappings ( )
192213 {
193- get
194- {
195- return LazyInitializer . EnsureInitialized ( ref _mappings , createCollection ) ! ;
214+ var properties = propertyMapper ( this ) . ToList ( ) ;
215+ if ( properties . FirstOrDefault ( m => m . DeclaringClass != this ) is { } errorMapping )
216+ throw new InvalidOperationException ( $ "PropertyMapping ' { errorMapping . Name } ' is already used for another ClassMapping ' { errorMapping . DeclaringClass . Name } '." ) ;
196217
197- PropertyMappingCollection createCollection ( )
198- {
199- var properties = propertyMapper ( this ) . ToList ( ) ;
200- if ( properties . FirstOrDefault ( m => m . DeclaringClass != this ) is { } errorMapping )
201- throw new InvalidOperationException ( $ "PropertyMapping '{ errorMapping . Name } ' is already used for another ClassMapping '{ errorMapping . DeclaringClass . Name } '.") ;
202-
203- return new PropertyMappingCollection ( properties ) ;
204- }
205- }
218+ return new PropertyMappingCollection ( properties ) ;
206219 }
207220
208221 /// <summary>
@@ -215,7 +228,7 @@ PropertyMappingCollection createCollection()
215228 /// property will also be present in the PropertyMappings collection. If this class has
216229 /// no such property, it is null.
217230 /// </summary>
218- public PropertyMapping ? PrimitiveValueProperty => PropertyMappings . SingleOrDefault ( pm => pm . RepresentsValueElement ) ;
231+ public PropertyMapping ? PrimitiveValueProperty => PropertyMappingsInternal . PrimitiveValueProperty ;
219232
220233 /// <summary>
221234 /// This indicates that this class is representing the Patient data (and implements <see cref="IPatient"/>).
@@ -225,7 +238,7 @@ PropertyMappingCollection createCollection()
225238 /// <summary>
226239 /// Whether the reflected type has a member that represent a primitive value.
227240 /// </summary>
228- public bool HasPrimitiveValueMember => PropertyMappings . Any ( pm => pm . RepresentsValueElement ) ;
241+ public bool HasPrimitiveValueMember => PropertyMappingsInternal . HasPrimitiveValueMember ;
229242
230243 /// <summary>
231244 /// Returns the mapping for an element of this class by its name.
@@ -358,7 +371,7 @@ IReadOnlyCollection<IElementDefinitionSummary> IStructureDefinitionSummary.GetEl
358371 /// <remarks>If not set, the default constructor for the <see cref="NativeType"/> will be used.</remarks>
359372 public Base CreateInstance ( )
360373 {
361- var factory = LazyInitializer . EnsureInitialized ( ref _factory , NativeType . BuildFactoryMethod ) ! ;
374+ var factory = _factory ?? LazyInitializer . EnsureInitialized ( ref _factory , NativeType . BuildFactoryMethod ) ! ;
362375 var newInstance = factory ( ) ;
363376 if ( newInstance is IDynamicType idt ) idt . DynamicTypeName = Name ;
364377 return ( Base ) newInstance ;
@@ -372,7 +385,7 @@ public Base CreateInstance()
372385 /// <remarks>If not set, the default List constructor for the <see cref="NativeType"/> will be used.</remarks>
373386 public IList CreateList ( )
374387 {
375- var factory = LazyInitializer . EnsureInitialized ( ref _listFactory , NativeType . BuildListFactoryMethod ) ! ;
388+ var factory = _listFactory ?? LazyInitializer . EnsureInitialized ( ref _listFactory , NativeType . BuildListFactoryMethod ) ! ;
376389 return factory ( ) ;
377390 }
378391
0 commit comments