Drop the extra MruCache and resolve public fields lazily into the existing cache entry

This commit is contained in:
dualfroz committed 2026-10-11 18:59:41 +02:00
1 parent 7c96e99fb3
commit eb43ff4c98
2 files changed
+46 -20

No files matched your search

+28 -20
View File
@@ -49,8 +49,6 @@ namespace NLog.Internal
{ {
private MruCache<Type, ObjectPropertyInfos> ObjectTypeCache => _objectTypeCache ?? System.Threading.Interlocked.CompareExchange(ref _objectTypeCache, new MruCache<Type, ObjectPropertyInfos>(10000), null) ?? _objectTypeCache; private MruCache<Type, ObjectPropertyInfos> ObjectTypeCache => _objectTypeCache ?? System.Threading.Interlocked.CompareExchange(ref _objectTypeCache, new MruCache<Type, ObjectPropertyInfos>(10000), null) ?? _objectTypeCache;
private MruCache<Type, ObjectPropertyInfos>? _objectTypeCache; private MruCache<Type, ObjectPropertyInfos>? _objectTypeCache;
private MruCache<Type, ObjectPropertyInfos> ObjectTypeFieldCache => _objectTypeFieldCache ?? System.Threading.Interlocked.CompareExchange(ref _objectTypeFieldCache, new MruCache<Type, ObjectPropertyInfos>(10000), null) ?? _objectTypeFieldCache;
private MruCache<Type, ObjectPropertyInfos>? _objectTypeFieldCache;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private IObjectTypeTransformer ObjectTypeTransformation => _objectTypeTransformation ?? (_objectTypeTransformation = _serviceProvider?.GetService<IObjectTypeTransformer>() ?? this); private IObjectTypeTransformer ObjectTypeTransformation => _objectTypeTransformation ?? (_objectTypeTransformation = _serviceProvider?.GetService<IObjectTypeTransformer>() ?? this);
private IObjectTypeTransformer? _objectTypeTransformation; private IObjectTypeTransformer? _objectTypeTransformation;
@@ -97,13 +95,14 @@ namespace NLog.Internal
} }
var objectType = value.GetType(); var objectType = value.GetType();
var typeCache = includeFields ? ObjectTypeFieldCache : ObjectTypeCache; var propertyInfos = CheckForObjectProperties(value) ? BuildObjectPropertyInfos(value, includeFields) : ObjectPropertyInfos.SimpleToString;
if (!typeCache.TryGetValue(objectType, out var propertyInfos)) if (includeFields && propertyInfos.FieldLookup is null)
{ {
propertyInfos = CheckForObjectProperties(value) ? BuildObjectPropertyInfos(value, includeFields) : ObjectPropertyInfos.SimpleToString; // Remember that the public fields were resolved, so the type is not reflected again
typeCache.TryAddValue(objectType, propertyInfos); propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, propertyInfos.FastLookup, ArrayHelper.Empty<FastPropertyLookup>());
} }
return new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup); ObjectTypeCache.TryAddValue(objectType, propertyInfos);
return propertyInfos.CreatePropertyList(value, includeFields);
} }
/// <summary> /// <summary>
@@ -162,15 +161,15 @@ namespace NLog.Internal
#endif #endif
Type objectType = value.GetType(); Type objectType = value.GetType();
if (!includeFields && ObjectTypeCache.TryGetValue(objectType, out var propertyInfos)) if (ObjectTypeCache.TryGetValue(objectType, out var propertyInfos) && !(includeFields && propertyInfos.NeedsFieldLookup))
{ {
if (!propertyInfos.HasFastLookup) if (!propertyInfos.HasFastLookup)
{ {
var fastLookup = BuildFastLookup(propertyInfos.Properties, false); var fastLookup = BuildFastLookup(propertyInfos.Properties, false);
propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, fastLookup); propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, fastLookup, propertyInfos.FieldLookup);
ObjectTypeCache.TryAddValue(objectType, propertyInfos); ObjectTypeCache.TryAddValue(objectType, propertyInfos);
} }
objectPropertyList = new ObjectPropertyList(value, propertyInfos.Properties, propertyInfos.FastLookup); objectPropertyList = propertyInfos.CreatePropertyList(value, includeFields);
return true; return true;
} }
@@ -200,15 +199,12 @@ namespace NLog.Internal
} }
else if (properties.Length == 0) else if (properties.Length == 0)
{ {
// Nothing to show as properties, so fall back to the public fields when asked to. if (!includeFields)
var fields = includeFields ? GetPublicFields(objectType) : ArrayHelper.Empty<FieldInfo>();
if (fields.Length > 0)
{
// Fields are only reachable through the fast-lookup, so it cannot be built lazily
return new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), BuildFieldLookup(fields));
}
return ObjectPropertyInfos.SimpleToString; return ObjectPropertyInfos.SimpleToString;
// Nothing to show as properties, so keep the public fields aside for the IncludePublicFields fallback
var fields = GetPublicFields(objectType);
return new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), ArrayHelper.Empty<FastPropertyLookup>(), BuildFieldLookup(fields));
} }
else else
{ {
@@ -626,20 +622,32 @@ namespace NLog.Internal
{ {
public readonly PropertyInfo[] Properties; public readonly PropertyInfo[] Properties;
public readonly FastPropertyLookup[]? FastLookup; public readonly FastPropertyLookup[]? FastLookup;
/// <summary>Public fields of a type without public properties, null until resolved</summary>
public readonly FastPropertyLookup[]? FieldLookup;
public static readonly ObjectPropertyInfos SimpleToString = new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), ArrayHelper.Empty<FastPropertyLookup>()); public static readonly ObjectPropertyInfos SimpleToString = new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), ArrayHelper.Empty<FastPropertyLookup>());
public ObjectPropertyInfos(PropertyInfo[] properties, FastPropertyLookup[]? fastLookup) public ObjectPropertyInfos(PropertyInfo[] properties, FastPropertyLookup[]? fastLookup, FastPropertyLookup[]? fieldLookup = null)
{ {
Properties = properties; Properties = properties;
FastLookup = fastLookup; FastLookup = fastLookup;
FieldLookup = fieldLookup;
} }
public bool HasFastLookup => FastLookup != null; public bool HasFastLookup => FastLookup != null;
public bool NeedsFieldLookup => FieldLookup is null && Properties.Length == 0 && FastLookup?.Length == 0;
public ObjectPropertyList CreatePropertyList(object value, bool includeFields)
{
if (includeFields && FieldLookup?.Length > 0)
return new ObjectPropertyList(value, Properties, FieldLookup);
return new ObjectPropertyList(value, Properties, FastLookup);
}
public bool Equals(ObjectPropertyInfos other) public bool Equals(ObjectPropertyInfos other)
{ {
return ReferenceEquals(Properties, other.Properties) && FastLookup?.Length == other.FastLookup?.Length; return ReferenceEquals(Properties, other.Properties) && FastLookup?.Length == other.FastLookup?.Length && ReferenceEquals(FieldLookup, other.FieldLookup);
} }
} }
@@ -246,6 +246,24 @@ namespace NLog.UnitTests.Targets
Assert.Equal(@"{""Name"":""John"",""Age"":42}", withFieldsAgain.ToString()); Assert.Equal(@"{""Name"":""John"",""Age"":42}", withFieldsAgain.ToString());
} }
[Fact]
public void IncludePublicFields_SameTypeWithoutOptionFirst_StillSerializesFieldsLater()
{
var testObject = new ClassWithOnlyFields { Name = "John", Age = 42 };
var jsonSerializer = new DefaultJsonSerializer(null);
var withoutFields = new StringBuilder();
jsonSerializer.SerializeObject(testObject, withoutFields, new JsonSerializeOptions { IncludePublicFields = false });
var withFields = new StringBuilder();
jsonSerializer.SerializeObject(testObject, withFields, new JsonSerializeOptions { IncludePublicFields = true });
var withoutFieldsAgain = new StringBuilder();
jsonSerializer.SerializeObject(testObject, withoutFieldsAgain, new JsonSerializeOptions { IncludePublicFields = false });
Assert.DoesNotContain(@"""Age"":42", withoutFields.ToString());
Assert.Equal(@"{""Name"":""John"",""Age"":42}", withFields.ToString());
Assert.Equal(withoutFields.ToString(), withoutFieldsAgain.ToString());
}
[Fact] [Fact]
public void IncludePublicFields_ShadowedField_IsWrittenOnce() public void IncludePublicFields_ShadowedField_IsWrittenOnce()
{ {