A new version of Mappa (v10.2.0) has been released.

Mappa 10.2.0 expands mapping control with reference handling and compile-time cycle breaking, IQueryable projection, nested property paths, dependency-injection registrars, inaccessible-member mapping, object factories, before/after hooks, and richer enum / settings options. Benchmarks, coverage tooling, and generator performance also improve. Full changelog: v10.1.0โ€ฆv10.2.0

Breaking changes

When upgrading from 10.1.0, see the upgrade guide.

Relaxed nullability matching for existing map methods

  • Nested mappings may reuse an existing map method when nullability annotations differ slightly (#184, #272)
    • With #nullable enable, nested mappings may now invoke an existing map method on the mapper, a dependency, or a matching polymorphic method when the underlying types match even if nullability annotations differ.
    • Exact nullability matches are still preferred; a relaxed match is used only when no exact match exists.
    • Supported relaxations: nested needs TTarget? โ†’ may invoke a method returning TTarget; nested needs TSource โ†’ may invoke a method accepting TSource?.
    • Previously, a nullability mismatch caused the generator to skip the existing method and generate inline mapping (or select another strategy). Review nested mappings that relied on the old behaviour.

Whatโ€™s new

Reference handling

  • Circular reference / reference handling (#249, #307) โ€” Reuse mapped reference-type instances for cycles and shared graphs (ReferenceReusing), limit runtime nesting (MaxRuntimeDepth), and guard generator discovery (MaxCompileTimeDepth + compile-time mapping-cycle detection). Not supported on IQueryable projection.
  • Per-TTarget reference reuse and codegen polish (#320, #321, #322, #333) โ€” Key reuse by source identity and typeof(TTarget), omit redundant AddReferencePair calls, and cache the reference-manager local.
  • BreakCompileTimeCycles (#306, #311) โ€” Optionally break compile-time mapping cycles by synthesizing private map methods; emits MP00078 when a cycle is auto-broken.

New features

  • IQueryable projection (ProjectTo-style) (#245, #284) โ€” Map methods with IQueryable<TSource> โ†’ IQueryable<TTarget> emit provider-translatable Select projections. Not compatible with Native AOT ([RequiresDynamicCode]).
  • Nested (dot-separated) property paths (#120, #274, #275, #278) โ€” Use paths such as "Address.City" on MappaUseProperty, invoke, constant, context, and ignore attributes for flattening/unflattening.
  • MappaObjectFactory (#253, #291) โ€” Construct target types via named factory methods instead of new.
  • MappaBeforeMap / MappaAfterMap (#250, #281) โ€” Invoke named hooks immediately before and after the generated root mapping body.
  • MappaMustMapTargetProperty (#20, #298) โ€” Require listed (or all) non-required target properties to be mapped on the empty-constructor path; otherwise MP00065.
  • Inaccessible / private members (#254, #300) โ€” Opt in with MappaAllowInaccessibleSourceMembers / MappaAllowInaccessibleTargetMembers to read/write private or protected members (and invoke inaccessible constructors) via UnsafeAccessor.
  • Generic polymorphic type-mapping attributes (#305, #316) โ€” Prefer MappaTypeMapping<TTarget, TSource> and MappaTypeMappingDefault<TDefault> over non-generic forms.

Dependency injection

  • MappaDependencyInjection attribute (#299, #302) โ€” Generate IServiceCollection registration methods for [Mappa] mappers; static DI mappers are skipped with MP00073.
  • InjectFromAssemblies (#301, #317) โ€” Discover [Mappa] mappers across assemblies for DI registration; Bson and Protobuf DI packages migrate to generated registrars.

MappaSettings and mapping behaviour

  • DictionaryAssignment (#34, #273) โ€” Choose indexer or Add insertion for dictionary-to-dictionary mapping.
  • PreventEnumerableCount (#108, #285) โ€” Avoid Enumerable.Count for fixed-size targets when the source length is unknown.
  • CompatibleMapMethod (#13, #296) โ€” Opt-in reuse of existing map methods when the source is a base/interface type and the method returns a compatible derived type.
  • Richer enum mapping configuration (#255, #282) โ€” Configure enumโ†”integral, enumโ†”string, and enumโ†”enum pairings via MappaMapEnumMember, MappaMapEnumIgnore, and MappaMapEnumDefault.
  • Relaxed nullability fallback for map method matching (#184, #272) โ€” See Breaking changes.

Projection polish

  • Remove unused {MethodName}Element from IQueryable projections (#303, #304) โ€” Stop emitting unused local helpers in projection-generated code.

Generator quality and performance

  • Narrow syntax providers and honor CancellationToken (#323, #325, #335) โ€” Attribute-driven incremental discovery via ForAttributeWithMetadataName, and cooperative cancellation on hot paths.
  • Reduce ReportGenerator CRAP / cyclomatic complexity (#238, #318) โ€” Refactor hotspot methods and enforce CRAP/CC maxima in the coverage script.
  • Branch coverage improvements (#327, #336) โ€” Targeted tests for previously uncovered generator branches; comparison-chart annotation polish.
  • Bug fixes (#319, #328, #329, #330) โ€” Null-class continue path, culture typo, and MappaContext documentation corrections.

Benchmarks and CI tooling

  • Expanded benchmark suite with SVG charts (#134, #286) โ€” Broader mapper scenarios, competitor/Mappa percentage charts, and gist/history publishing.
  • Benchmark display and comparison charts (#295, #297, #309, #315) โ€” Bogus-seeded inputs, refined TIME/MEMORY SVGs, and a winner comparison table.
  • Default BenchmarkDotNet job; skip PR benchmarks (#331, #332) โ€” Reduce flakiness and stop running/commenting benchmarks on pull requests.
  • Coverage history and PR coverage comments (#250, #276, #280, #281) โ€” PR coverage comments with drop thresholds and improved coverage-history SVGs.
  • Gist update retries (#293, #294) โ€” Idempotent history merges with retries on gist updates.

Test infrastructure

  • Stronger generator syntax assertions (#277, #279, #290, #292) โ€” Require HasNextSyntaxNode walks and full validation after HaveGeneratedSourceCode().
  • Diagnostic assertion rules (#310, #313) โ€” Require HaveDiagnostics(n) or NotHaveDiagnostics() on generator test chains.
  • Dump generated sources from generator tests (#308, #312) โ€” Persist successful generation dumps for issue reports.

Maintenance

  • Update outdated NuGet package dependencies (#287, #337) โ€” Bump Roslyn, analyzers, test packages, samples, benchmarks, and integration packages.
  • Release 10.2.0 (#271, #338) โ€” Promote version from 10.2.0-alpha.* to stable 10.2.0.

Whatโ€™s next

This is a tentative and non exhaustive list of issues that I would like to cover in the upcoming version of Mappa:

Mapping / generator features

  • #334 โ€” Inline mapping via C# interceptors
  • #256 โ€” Collection merge semantics and map into existing collections
  • #251 โ€” Conditional mapping, null substitution, and value transformers
  • #248 โ€” Generic mapping methods
  • #247 โ€” Map into existing target / merge semantics
  • #246 โ€” Reverse / bidirectional mapping
  • #283 โ€” IQueryable projection follow-ups
  • #73 โ€” Support fields, not only properties

Generator performance

  • #326 โ€” Optimize hot paths (TypeSymbolExtensions, constructor detection, attribute helpers)
  • #324 โ€” Reduce Collect() fan-out and document incremental host behavior
  • #314 โ€” Investigate Mapperly/Mapster benchmark wins and FastCollections behaviour

Diagnostics and maintainability

  • #141 โ€” Improved debug feature