ImmutaMap 9.2.2

dotnet add package ImmutaMap --version 9.2.2
                    
NuGet\Install-Package ImmutaMap -Version 9.2.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ImmutaMap" Version="9.2.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ImmutaMap" Version="9.2.2" />
                    
Directory.Packages.props
<PackageReference Include="ImmutaMap" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ImmutaMap --version 9.2.2
                    
#r "nuget: ImmutaMap, 9.2.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ImmutaMap@9.2.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ImmutaMap&version=9.2.2
                    
Install as a Cake Addin
#tool nuget:?package=ImmutaMap&version=9.2.2
                    
Install as a Cake Tool

ImmutaMap

High‑performance, allocation‑aware mapping for immutable (and mutable) .NET types.

Seamlessly map between records, classes, anonymous types, and dynamic targets with inline configuration, attribute / type transforms, and async support.

Why ImmutaMap?

Most mappers were designed around mutable POCOs. When you lean into C# records and immutable design, you either:

  • Lose ergonomics (manual constructor plumbing everywhere), or
  • Fall back to reflection (slow, alloc heavy), or
  • Maintain piles of hand‑written projection code.

ImmutaMap focuses on immutable‑first scenarios while still supporting classic mutable types. It compiles mapping plans (constructor + property delegates), caches them, and applies only the transforms you ask for.

Key capabilities:

  • Map record → class, class → record, class → class, record → record.
  • Inline fluent configuration (rename, skip, transform property / type / attribute).
  • Sync & async transforms (per property or per type) without changing your domain models.
  • Update immutable instances using With (expression or anonymous type) – new instance every time.
  • Copy into existing mutable targets (Copy, CopyAsync), including name remaps.
  • Attribute‑driven transforms (source & target side) with full control.
  • Dynamic mapping (ToDynamic) – shape an object on the fly.

Full test coverage examples: TargetBuilderTests.cs

Quick Start

Install via source today (NuGet packaging WIP):

git clone https://github.com/TripleG3/ImmutaMap.git
cd ImmutaMap
dotnet build

Reference ImmutaMap from your project, then:

var person = new PersonRecord("Mike", "Doe", 42);
var dto = person.To<PersonRecord, PersonDto>();

Core API Surface

Method Purpose Immutable Safe Notes
To<TTarget>() Simple 1:1 map Uses plan cache & constructor fast path where possible.
To<TSource,TTarget>(cfg => …) Configured map Rename, skip, transforms, etc.
ToAsync<TSource,TTarget>(cfg => …) Async configured map Awaited property/type transforms.
With(expr, value) Replace one property New instance (source untouched).
With(expr, func) Compute new property from old Executes func lazily at build.
With(anonymous) Apply multiple property values Only matching names considered.
Copy(source) Copy into existing target N/A Mutates target in place.
Copy(source, cfg => …) Configured in‑place copy N/A Rename + transforms allowed.
CopyAsync(source, cfg => …) Async in‑place copy N/A Async transforms awaited.
ToDynamic() Shape to dynamic Produces an ExpandoObject-like anonymous result.
MapName Rename property mapping Source→Target pair registration.
Skip(expr) Exclude property Name case handling via IgnoreCase.
MapPropertyType(expr, func) Single property transform Adds a property transformer.
MapSourceAttribute<TAttr> Source attribute transform Provide (attr,value) → new value.
MapTargetAttribute<TAttr> Target attribute transform Same but runs targeting target property metadata.
MapType<TType> Global type transform (sync) All matching source values run through delegate.
MapTypeAsync<TType> Global type transform (async) ValueTask/Task path.

Examples

1. Simple Map (record → class)

var record = new PersonRecord("Mike", "Doe", 42);
var cls = record.To<PersonRecord, PersonClass>(_ => { });

2. Rename a Property

var employee = record.To<PersonRecord, Employee>(cfg =>
{
	cfg.MapName(r => r.FirstName, e => e.GivenName)
		.MapName(r => r.LastName,  e => e.Surname);
});

3. Skip & Case‑Insensitive Mapping

var model = something.To<Source, Target>(cfg =>
{
	cfg.IgnoreCase = true; // 'firstname' -> 'FirstName'
	cfg.Skip(s => s.Secret);
});

4. Per‑Property Transform

var updated = order.To<Order, OrderDto>(cfg =>
{
	cfg.MapPropertyType(o => o.Total, total => Math.Round(total, 2));
});

5. Attribute‑Driven Transform (Source)

var result = person.To<PersonRecord, PersonClass>(cfg =>
{
	cfg.MapSourceAttribute<FirstNameAttribute>((attr, value) => attr.RealName);
});

6. Global Type Transform

var message = dto.To<MessageDto, Message>(cfg =>
{
	cfg.MapType<DateTime>(d => d.ToLocalTime());
});

7. Async Type Transform

var msg = await dto.ToAsync<MessageDto, Message>(cfg =>
{
	cfg.MapTypeAsync<DateTime>(async d => d.ToLocalTime());
});

8. Updating Immutable Instance (With)

var person = new PersonRecord("Mike", "Doe", 42);
var older = person.With(p => p.Age, age => age + 1);
var renamed = person.With(new { FirstName = "John" });

9. Copy Into Existing Mutable Target

target.Copy(source); // shallow mapped properties by name

target.Copy(source, cfg =>
{
	cfg.MapName(s => s.Counter, t => t.Count)
		.MapName(s => s.Item_2,  t => t.Item2);
});

10. Dynamic Mapping

dynamic shaped = person.ToDynamic(cfg =>
{
	cfg.Skip(p => p.Age);
});
Console.WriteLine(shaped.FirstName);

Async Patterns

Async overloads mirror sync but use AsyncConfiguration<,> and accept:

  • MapTypeAsync<T>(Func<T, Task<object?>>)
  • MapPropertyTypeAsync(expr, ValueTask<object>)

They can be combined with sync transforms.

Error Handling

Set WillNotThrowExceptions = true in a configuration to suppress mapper exceptions (they will be swallowed). Default is to throw mapping issues (e.g., access / type errors) to surface problems early.

Performance Notes

Benchmarks (representative, will vary):

Scenario Library (µs/ns) Manual (ns) Notes
Record→Class (no transforms) ~12 µs ~6 ns Dominated by property graph & delegate path; manual is raw ctor.
DTO→Record w/ ctor fast path tens of ns ~5–6 ns Uses compiled constructor delegate.
DateTime global transform Adds minor delegate overhead N/A Applied per matching property.

Optimization features:

  • Plan & constructor delegate caching.
  • Compiled expression getters / setters (backing field for init‑only).
  • Fast path when no transformers present.
  • Async path defers allocation until needed.

When to Use / When Not to Use

Use ImmutaMap when:

  • You work heavily with records / immutable objects.
  • You need inline, one‑off mapping logic without global profiles.
  • You want attribute or type‑wide transforms without ceremony.

Avoid when:

  • You require runtime code generation across assemblies (not exposed yet).
  • You need precompiled static source generators (future consideration).

Extension Points

Create custom transformers by implementing:

  • ITransformer (sync)
  • IAsyncTransformer (async) Add them to configuration.Transformers / configuration.AsyncTransformers.

Roadmap (Planned)

  • Bulk compiled property copy delegate (remove per‑property loop cost).
  • Specialized typed delegate paths (reduced boxing / indirection).
  • Dynamic source fast‑plan caching.
  • Optional source generator package.

Contributing

  1. Fork & branch from main.
  2. Add/modify tests in ImmutaMap.Test for new behavior.
  3. Run: dotnet test (all green) & dotnet run --project ImmutaMap.Benchmarks if perf related.
  4. Submit PR with rationale + benchmark deltas (if perf change).

Minimal End‑to‑End Sample

var record = new PersonRecord("First", "Last", 30);
var enriched = record
	.To<PersonRecord, Employee>(cfg =>
	{
		cfg.MapName(r => r.FirstName, e => e.GivenName)
		   .MapType<DateTime>(d => d.ToLocalTime());
	})
	.With(e => e.GivenName, name => name.ToUpper());

// Copy into existing target
var target = new Employee();
target.Copy(enriched, cfg => cfg.MapName(e => e.GivenName, t => t.DisplayName));

License

MIT. See LICENSE.


Questions / ideas? Open an issue or PR – feedback drives the roadmap.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
9.2.2 8 9/3/2025
9.1.10 206 1/21/2024
9.1.9 125 1/21/2024
9.1.8 1,316 10/26/2023
9.1.7 152 10/26/2023
9.1.6 143 10/26/2023
9.1.5 164 10/26/2023
9.1.4 155 10/26/2023
9.1.3 156 10/25/2023
9.0.0 171 10/25/2023
8.0.1 291 10/12/2023
7.0.1 374 8/12/2023
7.0.0 659 4/4/2023
6.0.0 545 9/1/2022
2.5.3 764 8/31/2022
2.5.2 1,067 4/19/2021
2.5.1 501 3/25/2021
2.5.0 560 3/17/2021
2.4.1 470 3/15/2021
2.4.0 451 3/15/2021
2.3.0 435 3/15/2021
2.2.0 442 3/12/2021
2.1.0 505 3/11/2021
2.0.0 452 3/7/2021
1.0.9 553 2/1/2021
1.0.8 595 1/10/2021
1.0.7 590 1/10/2021
1.0.6 531 1/9/2021
1.0.5 542 1/8/2021
1.0.4 485 1/7/2021
1.0.3 476 1/6/2021
1.0.2 475 1/6/2021
1.0.1 505 12/21/2020
1.0.0 480 12/21/2020