Skip to content

refactor: reduce boxing in runtime collection helpers #107

@j-d-ha

Description

@j-d-ha

Summary

The immediate performance target is the runtime project, not the generator.

src/LayeredCraft.DynamoMapper.Runtime/AttributeValueExtensions/CollectionAttributeValueExtensions.cs currently does a large amount of boxing/unboxing in its generic list/map/set helper paths, especially when converting collection elements for scalar and enum types.

Scope

This issue is specifically about runtime mapping performance in LayeredCraft.DynamoMapper.Runtime.

It is not about MapperSyntaxProvider, incremental generator analysis, or attribute option parsing in the generator project.

Current Behavior

The collection helpers route generic element conversion through these methods in CollectionAttributeValueExtensions.cs:

  • ConvertFromAttributeValue<T>(AttributeValue av, string? format = null)
  • ConvertToAttributeValue<T>(T? value, string? format = null)
  • ParseNumber<T>(string s) where T : struct
  • FormatNumber<T>(T value) where T : struct

Those helpers are used by the public runtime APIs for:

  • TryGetList<T>() / GetList<T>() / SetList<T>()
  • TryGetMap<T>() / GetMap<T>() / SetMap<T>()
  • TryGetNumberSet<T>() / GetNumberSet<T>() / SetNumberSet<T>()

The implementation currently relies on patterns such as:

  • (T)(object)...
  • ((int)(object)value)
  • ((Guid)(object)value)
  • ((Enum)(object)value)

That means common runtime collection paths box and unbox repeatedly for primitives, nullable primitives, date/time values, GUIDs, and enums.

Evidence in Code

In CollectionAttributeValueExtensions.cs the boxing-heavy section is concentrated in the private helper block near the bottom of the file.

Examples include:

  • ConvertFromAttributeValue<T>() returning values via (T)(object) for bool, numeric types, DateTime, DateTimeOffset, TimeSpan, DateOnly, TimeOnly, Guid, byte[], MemoryStream, and Stream
  • ConvertToAttributeValue<T>() casting incoming values back out via (object) before formatting/serializing them
  • ParseNumber<T>() returning parsed numeric values via (T)(object)
  • enum serialization going through ((Enum)(object)value).ToString(format)

Important Context

The runtime project already has type-specific helper surfaces in separate files:

  • NumericAttributeValueExtensions.cs
  • EnumAttributeValueExtensions.cs
  • DateTimeAttributeValueExtensions.cs
  • DateOnlyTimeOnlyAttributeValueExtensions.cs
  • GuidAttributeValueExtensions.cs
  • BooleanAttributeValueExtensions.cs
  • StringAttributeValueExtensions.cs
  • BinaryAttributeValueExtensions.cs

So the main problem is not lack of type-specific logic. It is that the generic collection helper layer currently collapses many supported element types back into object-based conversion.

Proposed Fix

Refactor the runtime collection conversion path so supported element types do not need to round-trip through object in the hot path.

Possible directions:

  1. Split common supported element types into strongly typed branches or specialized generic fast paths.
  2. Reuse existing runtime type-specific helpers where practical instead of re-parsing/re-formatting through object casts.
  3. Keep unsupported types failing with the current NotSupportedException behavior.
  4. Preserve existing null-handling, format-handling, and Dynamo kind behavior.

Acceptance Criteria

  • The issue is resolved in the runtime project, not the generator project.
  • CollectionAttributeValueExtensions.cs no longer relies on repeated (object) boxing/unboxing in the main generic collection conversion path for supported scalar/enum element types.
  • Behavior stays the same for null collection elements, nullable element types, format-sensitive types, and supported set/list/map operations.
  • Tests cover the affected runtime paths, especially primitives, enums, GUID/date-time types, and nullable collection elements.
  • Some form of allocation/perf validation is included so improvements can be verified and regressions are easier to catch later.

Metadata

Metadata

Assignees

No one assigned

    Labels

    internalInternal change, exclude from release notestype: refactorCode refactoring

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions