diff --git a/src/HotChocolate/Core/src/Types/SchemaBuilder.Setup.cs b/src/HotChocolate/Core/src/Types/SchemaBuilder.Setup.cs
index cc7e19078e3..f0c1f0196e8 100644
--- a/src/HotChocolate/Core/src/Types/SchemaBuilder.Setup.cs
+++ b/src/HotChocolate/Core/src/Types/SchemaBuilder.Setup.cs
@@ -9,6 +9,7 @@
using HotChocolate.Types;
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Factories;
+using HotChocolate.Types.Helpers;
using HotChocolate.Types.Interceptors;
using HotChocolate.Utilities;
using HotChocolate.Utilities.Introspection;
@@ -71,6 +72,10 @@ public static Schema Create(SchemaBuilder builder)
context.SchemaInterceptor.OnError(context, ex);
throw;
}
+ finally
+ {
+ TypeMemHelper.Clear();
+ }
}
public static DescriptorContext CreateContext(
diff --git a/src/HotChocolate/Core/src/Types/Types/Contracts/IFieldCollection.cs b/src/HotChocolate/Core/src/Types/Types/Contracts/IFieldCollection.cs
index 52a34955f58..a25c89efbd2 100644
--- a/src/HotChocolate/Core/src/Types/Types/Contracts/IFieldCollection.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Contracts/IFieldCollection.cs
@@ -5,28 +5,63 @@
namespace HotChocolate.Types;
+///
+/// Represents the field collection of a type.
+///
+///
+/// The field type.
+///
public interface IFieldCollection : IReadOnlyCollection where T : class, IField
{
+ ///
+ /// Gets a field by its name.
+ ///
T this[string fieldName] { get; }
+ ///
+ /// Gets a field by its index.
+ ///
T this[int index] { get; }
+ ///
+ /// Checks if a field with the specified
+ /// exists in this collection.
+ ///
+ ///
+ /// The name of a field.
+ ///
+ ///
+ /// Returns true if a field with the specified exists;
+ /// otherwise, false.
+ ///
bool ContainsField(string fieldName);
}
+///
+/// This helper class provides extensions to the interface
+/// to allow for more efficiency when using the interface.
+///
public static class FieldCollectionExtensions
{
+ ///
+ /// Tries to get a field by its name from the field collection.
+ ///
+ ///
+ /// The type of the field.
+ ///
public static bool TryGetField(
this IFieldCollection collection,
string fieldName,
[NotNullWhen(true)] out T? field)
where T : class, IField
{
+ // if we use the default implementation we will use the TryGetField method on there.
if (collection is FieldCollection fc)
{
return fc.TryGetField(fieldName, out field);
}
+ // in any other case we simulate the behavior which is not as efficient.
if (collection.ContainsField(fieldName))
{
field = collection[fieldName];
diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/InputObjectTypeDescriptor.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/InputObjectTypeDescriptor.cs
index ace88ce4248..640ab7452f1 100644
--- a/src/HotChocolate/Core/src/Types/Types/Descriptors/InputObjectTypeDescriptor.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/InputObjectTypeDescriptor.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -13,6 +15,8 @@ public class InputObjectTypeDescriptor
: DescriptorBase
, IInputObjectTypeDescriptor
{
+ private readonly List _fields = new();
+
protected InputObjectTypeDescriptor(IDescriptorContext context, Type runtimeType)
: base(context)
{
@@ -43,14 +47,14 @@ protected InputObjectTypeDescriptor(IDescriptorContext context)
foreach (var field in definition.Fields)
{
- Fields.Add(InputFieldDescriptor.From(Context, field));
+ _fields.Add(InputFieldDescriptor.From(Context, field));
}
}
protected internal override InputObjectTypeDefinition Definition { get; protected set; } =
new();
- protected List Fields { get; } = new();
+ protected ICollection Fields => _fields;
protected override void OnCreateDefinition(
InputObjectTypeDefinition definition)
@@ -64,28 +68,77 @@ protected InputObjectTypeDescriptor(IDescriptorContext context)
Definition.AttributesAreApplied = true;
}
- var fields = new Dictionary();
- var handledProperties = new HashSet();
+ var fields = TypeMemHelper.RentInputFieldDefinitionMap();
+ var handledMembers = TypeMemHelper.RentMemberSet();
- FieldDescriptorUtilities.AddExplicitFields(
- Fields.Select(t => t.CreateDefinition()),
- f => f.Property,
- fields,
- handledProperties);
+ foreach (var fieldDescriptor in _fields)
+ {
+ var fieldDefinition = fieldDescriptor.CreateDefinition();
- OnCompleteFields(fields, handledProperties);
+ if (!fieldDefinition.Ignore && !string.IsNullOrEmpty(fieldDefinition.Name))
+ {
+ fields[fieldDefinition.Name] = fieldDefinition;
+ }
+ if (fieldDefinition.Property is { } prop)
+ {
+ handledMembers.Add(prop);
+ }
+ }
+
+ OnCompleteFields(fields, handledMembers);
+
+ Definition.Fields.Clear();
Definition.Fields.AddRange(fields.Values);
+ TypeMemHelper.Return(fields);
+ TypeMemHelper.Return(handledMembers);
+
base.OnCreateDefinition(definition);
}
- protected virtual void OnCompleteFields(
+ protected void InferFieldsFromFieldBindingType(
IDictionary fields,
- ISet handledProperties)
+ ISet handledMembers)
{
+ if (Definition.Fields.IsImplicitBinding())
+ {
+ var inspector = Context.TypeInspector;
+ var naming = Context.Naming;
+ var type = Definition.RuntimeType;
+ var members = inspector.GetMembers(type);
+
+ foreach (var member in members)
+ {
+ if (member.MemberType is MemberTypes.Property)
+ {
+ var name = naming.GetMemberName(member, MemberKind.InputObjectField);
+
+ if (handledMembers.Add(member) &&
+ !fields.ContainsKey(name))
+ {
+ var descriptor = InputFieldDescriptor.New(
+ Context,
+ (PropertyInfo)member);
+
+ _fields.Add(descriptor);
+ handledMembers.Add(member);
+
+ // the create definition call will trigger the OnCompleteField call
+ // on the field description and trigger the initialization of the
+ // fields arguments.
+ fields[name] = descriptor.CreateDefinition();
+ }
+ }
+ }
+ }
}
+ protected virtual void OnCompleteFields(
+ IDictionary fields,
+ ISet handledProperties)
+ { }
+
public IInputObjectTypeDescriptor SyntaxNode(
InputObjectTypeDefinitionNode inputObjectTypeDefinition)
{
@@ -107,7 +160,7 @@ public IInputObjectTypeDescriptor Description(string value)
public IInputFieldDescriptor Field(string name)
{
- var fieldDescriptor = Fields.FirstOrDefault(t => t.Definition.Name.EqualsOrdinal(name));
+ var fieldDescriptor = _fields.Find(t => t.Definition.Name.EqualsOrdinal(name));
if (fieldDescriptor is not null)
{
@@ -115,7 +168,7 @@ public IInputFieldDescriptor Field(string name)
}
fieldDescriptor = new InputFieldDescriptor(Context, name);
- Fields.Add(fieldDescriptor);
+ _fields.Add(fieldDescriptor);
return fieldDescriptor;
}
diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/InputObjectTypeDescriptor~1.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/InputObjectTypeDescriptor~1.cs
index b2dcd073de7..c407f95a414 100644
--- a/src/HotChocolate/Core/src/Types/Types/Descriptors/InputObjectTypeDescriptor~1.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/InputObjectTypeDescriptor~1.cs
@@ -33,14 +33,14 @@ protected internal InputObjectTypeDescriptor(IDescriptorContext context)
protected override void OnCompleteFields(
IDictionary fields,
- ISet handledProperties)
+ ISet handledProperties)
{
if (Definition.Fields.IsImplicitBinding())
{
FieldDescriptorUtilities.AddImplicitFields(
this,
p => InputFieldDescriptor
- .New(Context, p)
+ .New(Context, (PropertyInfo)p)
.CreateDefinition(),
fields,
handledProperties);
diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectTypeDescriptor.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectTypeDescriptor.cs
index 28f6d61f37f..2778aaeddd8 100644
--- a/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectTypeDescriptor.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectTypeDescriptor.cs
@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Threading;
using HotChocolate.Language;
using HotChocolate.Types.Descriptors.Definitions;
using HotChocolate.Types.Helpers;
@@ -18,11 +15,9 @@ namespace HotChocolate.Types.Descriptors;
public class ObjectTypeDescriptor
: DescriptorBase
- , IObjectTypeDescriptor
+ , IObjectTypeDescriptor
{
private readonly List _fields = new();
- private static Dictionary? _definitionMap = null;
- private static HashSet? _memberSet = null;
protected ObjectTypeDescriptor(IDescriptorContext context, Type clrType)
: base(context)
@@ -52,7 +47,7 @@ protected ObjectTypeDescriptor(IDescriptorContext context)
foreach (var field in definition.Fields)
{
- Fields.Add(ObjectFieldDescriptor.From(Context, field));
+ _fields.Add(ObjectFieldDescriptor.From(Context, field));
}
}
@@ -84,10 +79,8 @@ protected ObjectTypeDescriptor(IDescriptorContext context)
}
}
- var fields = Interlocked.Exchange(ref _definitionMap, null) ??
- new Dictionary();
- var handledMembers =
- Interlocked.Exchange(ref _memberSet, null) ?? new HashSet();
+ var fields = TypeMemHelper.RentObjectFieldDefinitionMap();
+ var handledMembers = TypeMemHelper.RentMemberSet();
foreach (var fieldDescriptor in _fields)
{
@@ -116,29 +109,21 @@ protected ObjectTypeDescriptor(IDescriptorContext context)
Definition.Fields.Clear();
Definition.Fields.AddRange(fields.Values);
- fields.Clear();
- handledMembers.Clear();
-
- Interlocked.CompareExchange(ref _definitionMap, fields, null);
- Interlocked.CompareExchange(ref _memberSet, handledMembers, null);
+ TypeMemHelper.Return(fields);
+ TypeMemHelper.Return(handledMembers);
base.OnCreateDefinition(definition);
}
internal void InferFieldsFromFieldBindingType()
{
- var fields = Interlocked.Exchange(ref _definitionMap, null) ??
- new Dictionary();
- var handledMembers =
- Interlocked.Exchange(ref _memberSet, null) ?? new HashSet();
+ var fields = TypeMemHelper.RentObjectFieldDefinitionMap();
+ var handledMembers = TypeMemHelper.RentMemberSet();
InferFieldsFromFieldBindingType(fields, handledMembers);
- fields.Clear();
- handledMembers.Clear();
-
- Interlocked.CompareExchange(ref _definitionMap, fields, null);
- Interlocked.CompareExchange(ref _memberSet, handledMembers, null);
+ TypeMemHelper.Return(fields);
+ TypeMemHelper.Return(handledMembers);
}
protected void InferFieldsFromFieldBindingType(
@@ -176,7 +161,7 @@ internal void InferFieldsFromFieldBindingType()
descriptor.Ignore();
}
- Fields.Add(descriptor);
+ _fields.Add(descriptor);
handledMembers.Add(member);
// the create definition call will trigger the OnCompleteField call
@@ -305,7 +290,7 @@ public IObjectTypeDescriptor IsOfType(IsOfType? isOfType)
public IObjectFieldDescriptor Field(string name)
{
- var fieldDescriptor = Fields.FirstOrDefault(t => t.Definition.Name.EqualsOrdinal(name));
+ var fieldDescriptor = _fields.Find(t => t.Definition.Name.EqualsOrdinal(name));
if (fieldDescriptor is not null)
{
@@ -313,7 +298,7 @@ public IObjectFieldDescriptor Field(string name)
}
fieldDescriptor = ObjectFieldDescriptor.New(Context, name);
- Fields.Add(fieldDescriptor);
+ _fields.Add(fieldDescriptor);
return fieldDescriptor;
}
@@ -331,8 +316,7 @@ public IObjectFieldDescriptor Field(string name)
if (propertyOrMethod is PropertyInfo || propertyOrMethod is MethodInfo)
{
- var fieldDescriptor = Fields.FirstOrDefault(
- t => t.Definition.Member == propertyOrMethod);
+ var fieldDescriptor = _fields.Find(t => t.Definition.Member == propertyOrMethod);
if (fieldDescriptor is not null)
{
@@ -344,7 +328,7 @@ public IObjectFieldDescriptor Field(string name)
propertyOrMethod,
Definition.RuntimeType,
propertyOrMethod.ReflectedType ?? Definition.RuntimeType);
- Fields.Add(fieldDescriptor);
+ _fields.Add(fieldDescriptor);
return fieldDescriptor;
}
@@ -365,8 +349,7 @@ public IObjectFieldDescriptor Field(string name)
if (member is PropertyInfo or MethodInfo)
{
- var fieldDescriptor = Fields.FirstOrDefault(
- t => t.Definition.Member == member);
+ var fieldDescriptor = _fields.Find(t => t.Definition.Member == member);
if (fieldDescriptor is not null)
{
@@ -378,7 +361,7 @@ public IObjectFieldDescriptor Field(string name)
member,
Definition.RuntimeType,
typeof(TResolver));
- Fields.Add(fieldDescriptor);
+ _fields.Add(fieldDescriptor);
return fieldDescriptor;
}
@@ -389,7 +372,7 @@ public IObjectFieldDescriptor Field(string name)
propertyOrMethod,
Definition.RuntimeType,
typeof(TResolver));
- Fields.Add(fieldDescriptor);
+ _fields.Add(fieldDescriptor);
return fieldDescriptor;
}
diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectTypeDescriptorBase~1.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectTypeDescriptorBase~1.cs
index b856124b7be..23a6e3b7677 100644
--- a/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectTypeDescriptorBase~1.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/ObjectTypeDescriptorBase~1.cs
@@ -144,15 +144,11 @@ public new IObjectTypeDescriptor IsOfType(IsOfType isOfType)
public IObjectFieldDescriptor Field(
Expression> propertyOrMethod)
- {
- return base.Field(propertyOrMethod);
- }
+ => base.Field(propertyOrMethod);
public IObjectFieldDescriptor Field(
Expression> propertyOrMethod)
- {
- return base.Field(propertyOrMethod);
- }
+ => base.Field(propertyOrMethod);
public new IObjectTypeDescriptor Directive(
TDirective directiveInstance)
@@ -177,4 +173,3 @@ public new IObjectTypeDescriptor Directive()
return this;
}
}
-
diff --git a/src/HotChocolate/Core/src/Types/Types/FieldCollection.cs b/src/HotChocolate/Core/src/Types/Types/FieldCollection.cs
index 1c18afaaaba..4fdc3562433 100644
--- a/src/HotChocolate/Core/src/Types/Types/FieldCollection.cs
+++ b/src/HotChocolate/Core/src/Types/Types/FieldCollection.cs
@@ -56,6 +56,8 @@ public bool TryGetField(string fieldName, [NotNullWhen(true)] out T? field)
return false;
}
+ internal ReadOnlySpan AsSpan() => _fields;
+
public IEnumerator GetEnumerator()
=> _fields.Length == 0
? EmptyFieldEnumerator.Instance
diff --git a/src/HotChocolate/Core/src/Types/Types/Helpers/FieldDescriptorUtilities.cs b/src/HotChocolate/Core/src/Types/Types/Helpers/FieldDescriptorUtilities.cs
index 5c1eae5cb48..c30e1b71fc4 100644
--- a/src/HotChocolate/Core/src/Types/Types/Helpers/FieldDescriptorUtilities.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Helpers/FieldDescriptorUtilities.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;
-using System.Threading;
using HotChocolate.Internal;
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Descriptors.Definitions;
@@ -12,8 +11,6 @@ namespace HotChocolate.Types.Helpers;
public static class FieldDescriptorUtilities
{
- private static HashSet? _names = new(StringComparer.Ordinal);
-
public static void AddExplicitFields(
IEnumerable fieldDefinitions,
Func resolveMember,
@@ -111,7 +108,7 @@ public static class FieldDescriptorUtilities
if (member is MethodInfo method)
{
- var processedNames = Interlocked.Exchange(ref _names, null) ?? new();
+ var processedNames = TypeMemHelper.RentNameSet();
try
{
@@ -142,8 +139,7 @@ public static class FieldDescriptorUtilities
}
finally
{
- processedNames.Clear();
- Interlocked.CompareExchange(ref _names, processedNames, null);
+ TypeMemHelper.Return(processedNames);
}
}
}
diff --git a/src/HotChocolate/Core/src/Types/Types/Helpers/TypeMemHelper.cs b/src/HotChocolate/Core/src/Types/Types/Helpers/TypeMemHelper.cs
new file mode 100644
index 00000000000..f791868e00b
--- /dev/null
+++ b/src/HotChocolate/Core/src/Types/Types/Helpers/TypeMemHelper.cs
@@ -0,0 +1,85 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading;
+using HotChocolate.Types.Descriptors.Definitions;
+
+namespace HotChocolate.Types.Helpers;
+
+///
+/// This internal helper is used centralize rented maps and list during type initialization.
+/// This ensures that we can release these helper objects when the schema is created.
+///
+internal static class TypeMemHelper
+{
+ private static Dictionary? _objectFieldDefinitionMap;
+ private static Dictionary? _inputFieldDefinitionMap;
+ private static Dictionary? _inputFieldMap;
+ private static HashSet? _memberSet;
+ private static HashSet? _nameSet;
+
+ public static Dictionary RentObjectFieldDefinitionMap()
+ => Interlocked.Exchange(ref _objectFieldDefinitionMap, null) ??
+ new Dictionary(StringComparer.Ordinal);
+
+ public static void Return(Dictionary map)
+ {
+ map.Clear();
+ Interlocked.CompareExchange(ref _objectFieldDefinitionMap, map, null);
+ }
+
+ public static Dictionary RentInputFieldDefinitionMap()
+ => Interlocked.Exchange(ref _inputFieldDefinitionMap, null) ??
+ new Dictionary(StringComparer.Ordinal);
+
+ public static void Return(Dictionary map)
+ {
+ map.Clear();
+ Interlocked.CompareExchange(ref _inputFieldDefinitionMap, map, null);
+ }
+
+ public static Dictionary RentInputFieldMap()
+ => Interlocked.Exchange(ref _inputFieldMap, null) ??
+ new Dictionary(StringComparer.Ordinal);
+
+ public static void Return(Dictionary map)
+ {
+ map.Clear();
+ Interlocked.CompareExchange(ref _inputFieldMap, map, null);
+ }
+
+ public static HashSet RentMemberSet()
+ => Interlocked.Exchange(ref _memberSet, null) ??
+ new HashSet();
+
+ public static void Return(HashSet set)
+ {
+ set.Clear();
+ Interlocked.CompareExchange(ref _memberSet, set, null);
+ }
+
+ public static HashSet RentNameSet()
+ => Interlocked.Exchange(ref _nameSet, null) ??
+ new HashSet(StringComparer.Ordinal);
+
+ public static void Return(HashSet set)
+ {
+ set.Clear();
+ Interlocked.CompareExchange(ref _nameSet, set, null);
+ }
+
+ // We allow the helper to clear all pooled objects so that after
+ // building the schema we can release the memory.
+ // There is a risk of extra allocation here if we build
+ // multiple schemas at the same time.
+ public static void Clear()
+ {
+ Interlocked.Exchange(ref _objectFieldDefinitionMap, null);
+ Interlocked.Exchange(ref _inputFieldDefinitionMap, null);
+ Interlocked.Exchange(ref _inputFieldMap, null);
+ Interlocked.Exchange(ref _memberSet, null);
+ Interlocked.Exchange(ref _nameSet, null);
+ }
+}
diff --git a/src/HotChocolate/Core/src/Types/Utilities/Serialization/InputObjectCompiler.cs b/src/HotChocolate/Core/src/Types/Utilities/Serialization/InputObjectCompiler.cs
index c77b1c288d3..67f413ce3db 100644
--- a/src/HotChocolate/Core/src/Types/Utilities/Serialization/InputObjectCompiler.cs
+++ b/src/HotChocolate/Core/src/Types/Utilities/Serialization/InputObjectCompiler.cs
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using HotChocolate.Types;
+using HotChocolate.Types.Helpers;
using static HotChocolate.Utilities.Serialization.InputObjectConstructorResolver;
#nullable enable
@@ -22,8 +22,10 @@ internal static class InputObjectCompiler
InputObjectType inputType,
ConstructorInfo? constructor = null)
{
- var fields = CreateFieldMap(inputType);
- constructor ??= GetCompatibleConstructor(inputType.RuntimeType, fields);
+ var fields = TypeMemHelper.RentInputFieldMap();
+ BuildFieldMap(inputType, fields);
+
+ constructor ??= GetCompatibleConstructor(inputType.RuntimeType, inputType, fields);
var instance = constructor is null
? Expression.New(inputType.RuntimeType)
@@ -48,8 +50,6 @@ internal static class InputObjectCompiler
public static Action