MeliorMapper 1.0.0
dotnet add package MeliorMapper --version 1.0.0
NuGet\Install-Package MeliorMapper -Version 1.0.0
<PackageReference Include="MeliorMapper" Version="1.0.0" />
<PackageVersion Include="MeliorMapper" Version="1.0.0" />
<PackageReference Include="MeliorMapper" />
paket add MeliorMapper --version 1.0.0
#r "nuget: MeliorMapper, 1.0.0"
#:package MeliorMapper@1.0.0
#addin nuget:?package=MeliorMapper&version=1.0.0
#tool nuget:?package=MeliorMapper&version=1.0.0
MeliorMapper
MeliorMapper is a high-performance, expression-tree based object-object mapper for .NET.
It targets .NET 8, .NET 9, and .NET 10 with an identical public API on every framework.
Status: 1.0.0 - Phase 4 (LINQ, tooling, and ecosystem) complete. See ROADMAP.md for the full per-phase breakdown, and CHANGELOG.md for release history.
Why MeliorMapper
- No reflection at map time. All mapping logic is compiled once, per type pair, into cached expression-tree delegates. Reflection is used only while building that configuration.
- Thread-safe by design. The compiled delegate cache is a
ConcurrentDictionary, and everyMapcall gets its own resolution context - no locks on the hot path. - Convention-based, zero-config for simple DTOs. Nested types, collections, and dictionaries
encountered during mapping that weren't explicitly registered are mapped automatically by
matching member names - regardless of naming convention - so you don't have to declare every
nested
CreateMap. - Circular-reference safe. Reference-typed object graphs with cycles map without stack overflows and preserve reference identity across the cycle.
- AutoMapper-shaped configuration surface.
ForMember,ForPath,ReverseMap,Profiles, value resolvers/converters - the fluent API will feel immediately familiar.
Install
dotnet add package MeliorMapper
dotnet add package MeliorMapper.DependencyInjection # optional, for services.AddMeliorMapper(...)
Quick start
using MeliorMapper;
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, UserDto>();
});
var mapper = config.CreateMapper();
UserDto dto = mapper.Map<User, UserDto>(user);
Features (Phase 1 - core engine)
MapperConfiguration/IMapper/CreateMap<TSource, TDestination>()- Convention-based property and field matching
- Constructor mapping for destination types without a public parameterless constructor (including default parameter values for unmatched parameters)
- Nested object mapping
- Collection mapping:
List<T>, arrays,HashSet<T>/ISet<T>, and otherIEnumerable<T>-shaped destinations - Dictionary mapping (
IDictionary<TKey,TValue>andDictionary<TKey,TValue>) - Primitive, string, and numeric conversions (including a
Convert.ChangeTypefallback for otherwise-unconvertible pairs) - Nullable value type mapping
- Enum-to-enum, enum-to-string, and string-to-enum mapping
- Circular reference protection with reference-identity preservation
- Fully compiled, cached mapping delegates (
ConcurrentDictionary-backed, lock-free reads)
// Map onto an existing instance
mapper.Map(source, existingDestination);
// Map using the runtime type of a boxed source
UserDto dto = mapper.Map<UserDto>((object)user);
// Types not explicitly registered are still mapped by convention
var addressDto = mapper.Map<AddressDto>(address);
Features (Phase 2 - advanced configuration)
Profiles, assembly scanning, and DI
public class UserProfile : Profile
{
public UserProfile()
{
CreateMap<User, UserDto>();
}
}
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<UserProfile>();
// or: cfg.AddMapsFromAssembly(typeof(UserProfile).Assembly);
});
services.AddMeliorMapper(cfg => cfg.AddProfile<UserProfile>());
// registers MapperConfiguration and IMapper as singletons
ForMember / ForPath
cfg.CreateMap<Order, OrderDto>()
.ForMember(d => d.Total, opt => opt.MapFrom((s, _) => s.UnitPrice * s.Quantity))
.ForMember(d => d.Notes, opt => opt.NullSubstitute("(none)"))
.ForMember(d => d.Discount, opt => opt.Condition(s => s.IsMember))
.ForPath(d => d.Shipping.City, opt => opt.MapFrom(s => s.ShippingCity));
Per-member options: MapFrom (expression or Func), Ignore, Condition, PreCondition,
NullSubstitute, ConvertUsing (a Func or an IValueConverter<TSourceMember, TMember>),
ResolveUsing (a Func or an IValueResolver<TSource, TDestination, TMember>).
MapFrom expressions that are a simple chain of member accesses (s => s.Address.City) are
decomposed and emitted as null-safe inline code, exactly like automatic flattening below - no
delegate call, no NullReferenceException if Address is null. Anything else (method calls,
arithmetic, string interpolation) is compiled once, at configuration time, into a plain delegate.
ReverseMap, Include, IncludeBase, IncludeAllDerived
cfg.CreateMap<Address, AddressDto>().ReverseMap();
cfg.CreateMap<Animal, AnimalDto>()
.ForMember(d => d.Species, opt => opt.MapFrom(s => s.GetType().Name))
.IncludeAllDerived();
cfg.CreateMap<Dog, DogDto>(); // inherits the Species configuration above
Include/IncludeBase/IncludeAllDerived merge member configuration (ForMember, BeforeMap,
AfterMap) between a base and derived type-pair at configuration time. This is not yet runtime
polymorphic dispatch (mapping the runtime-derived type from a base-typed reference) - that is
planned for a later phase.
ConstructUsing / ConvertUsing / BeforeMap / AfterMap
cfg.CreateMap<OrderRow, Order>().ConstructUsing(row => new Order(row.Id, row.Sku));
cfg.CreateMap<Money, decimal>().ConvertUsing((m, _) => m.Amount);
cfg.CreateMap<Address, AddressDto>().ConvertUsing<UpperCaseAddressConverter>();
cfg.CreateMap<User, UserDto>()
.BeforeMap((src, dest) => dest.MappedAtUtc = DateTime.UtcNow)
.AfterMap((src, dest) => dest.DisplayName = dest.DisplayName.Trim());
Naming conventions and flattening
Member matching normalizes case and separators, so PascalCase, camelCase, snake_case,
kebab-case, and UPPER_CASE all match each other automatically - no configuration needed.
public class Customer { public Address Address { get; set; } }
public class Address { public string City { get; set; } }
public class CustomerDto { public string AddressCity { get; set; } }
cfg.CreateMap<Customer, CustomerDto>(); // AddressCity <- Customer.Address.City, automatically
Configuration validation
var config = new MapperConfiguration(cfg => cfg.CreateMap<User, UserDto>());
config.AssertConfigurationIsValid(); // throws MeliorMapperConfigurationException listing every
// destination member that isn't mapped, ignored, or
// constructor-populated
Features (Phase 3 - enterprise features)
Records, init-only properties, required members, private/internal setters
All of these already work with no special configuration, because the compiled expression trees
MeliorMapper generates bypass the C# compiler's restrictions on init/required (the CLR itself
never enforces them) and can invoke non-public property setters directly:
public record PersonRecord(int Id, string Name); // constructor-matched, as before
public class PersonDto
{
public required int Id { get; init; } // init-only + required
public string Name { get; internal set; } = ""; // internal setter
}
cfg.CreateMap<PersonSource, PersonDto>(); // just works
Generic and open-generic mapping
cfg.CreateMap<Container<int>, ContainerDto<int>>(); // ordinary closed generics - always worked
cfg.CreateMap(typeof(Repository<>), typeof(RepositoryDto<>)); // open-generic template; every
// closed instantiation is mapped
// by convention, compiled lazily
cfg.CreateMap(sourceType, destinationType); // also handy when types are only
// known via reflection (e.g. a loop)
Open-generic registrations carry no ForMember customization (there's no way to write a typed
member expression without a concrete type argument) and are skipped by AssertConfigurationIsValid.
Interface and abstract class mapping
Reading from an interface-typed source now sees members declared on every interface it extends,
not just the interface itself. Mapping into an interface or abstract class requires ConstructUsing
(there's no constructor to select otherwise) - MeliorMapper throws a clear
MeliorMapperConfigurationException if you don't provide one:
cfg.CreateMap<PersonSource, IPerson>().ConstructUsing(_ => new PersonImpl());
Dynamic mapping: ExpandoObject, Dictionary<string, object>, Dictionary<object, object>
ExpandoObject bag = mapper.Map<Address, ExpandoObject>(address); // one entry per member;
// nested complex members
// become nested dictionaries
Address back = mapper.Map<ExpandoObject, Address>(bag); // entries matched by
// normalized key name
Dictionary<object, object> is handled by the existing dictionary-to-dictionary path. Converting a
dictionary into a POCO necessarily inspects each value's runtime type (value.GetType()) - the
one deliberate, narrowly-scoped exception to "no reflection while mapping"; every statically-typed
mapping remains fully reflection-free.
Global filters: MaxDepth, PreserveReferences, IgnoreNullValues, AllowNullCollections, AllowNullDestinationValues
cfg.CreateMap<Node, NodeDto>().MaxDepth(3); // self-referencing trees: stop
// recursing past 3 levels (nulls out)
cfg.CreateMap<User, UserDto>()
.IgnoreNullValues() // null source value -> leave destination member as-is
.AllowNullCollections(false) // null source collection -> empty destination collection
.AllowNullDestinationValues(false); // null complex member -> new() instead of null
PreserveReferences() is accepted for API familiarity; MeliorMapper always preserves reference
identity across circular class graphs, so there's nothing extra to opt into.
Global value transformers
cfg.AddGlobalValueTransformer<string>(s => s?.Trim()!); // applied to every string member,
// across every map, after any
// ForMember override has resolved it
Runtime diagnostics
cfg.EnableDiagnostics(entry => logger.LogDebug(
"{Source} -> {Destination} in {Duration}", entry.SourceType, entry.DestinationType, entry.Duration));
config.Diagnostics.TotalMaps;
config.Diagnostics.CountFor<User, UserDto>();
Disabled by default - a single null check per top-level Map call, no Stopwatch, no counters,
until EnableDiagnostics is called.
Features (Phase 4 - LINQ, tooling, and ecosystem)
ProjectTo / IQueryable
var dtos = dbContext.Blogs.ProjectTo<Blog, BlogDto>(mapper).ToList();
Builds a pure LINQ expression tree - member access, object/collection initialization, simple
conversions only, with no call back into MeliorMapper anywhere in the tree - so EF Core (or any
other IQueryable provider) translates the whole projection instead of materializing entities into
memory first. Verified against a real SQLite-backed EF Core query, not just LINQ-to-Objects.
Supports direct/flattened/MapFrom(Expression) member matching and nested objects/collections;
throws a clear MeliorMapperConfigurationException for anything that can't be expressed as a pure
expression tree (MapFrom(Func), ResolveUsing, member ConvertUsing, Condition/PreCondition,
ConstructUsing, type-level ConvertUsing, BeforeMap/AfterMap, ForPath) - point that
particular map at .ToList() first and use Map() afterward instead.
Mapping report
File.WriteAllText("mapping-report.html", config.GenerateHtmlReport());
Static HTML report of every registered type map: which destination member binds to which source
member (direct/flattened/ForMember), which are unmapped or ignored, and which type-map-level
features (ConstructUsing, ConvertUsing, MaxDepth, before/after hooks) are in play. Purely a
diagnostic/documentation tool - it has no effect on mapping behavior.
Roslyn analyzer
MeliorMapper.Analyzers (add as a <PackageReference> with PrivateAssets="all") is a best-effort
syntax/symbol check - it doesn't replicate the runtime engine's flattening/naming-convention
normalization or execute MeliorMapper itself, so it may occasionally warn on something the engine
would actually resolve. Diagnostics:
| Id | Severity | What it flags |
|---|---|---|
| MELIORMAP001 | Warning | A destination member with no matching source member and no ForMember/ForPath in the chain (has a code fix: insert .ForMember(d => d.X, opt => opt.Ignore())). |
| MELIORMAP002 | Warning | CreateMap<T, T>() - source and destination are the same type, so the registration is a no-op. |
| MELIORMAP003 | Warning | The same source/destination type pair registered via CreateMap more than once in the compilation. |
| MELIORMAP004 | Info | Both CreateMap<A, B>() and CreateMap<B, A>() registered separately - suggests .ReverseMap() instead. |
| MELIORMAP005 | Warning | A Profile subclass defined but never passed to AddProfile (suppressed entirely if any AddMapsFromAssembly call exists anywhere, since that sweeps profiles up reflectively in a way this check can't verify). |
Deliberately not implemented: an "unused mapping" diagnostic (finding every real usage of a
registered pair across an arbitrary consuming codebase has a high false-positive rate that would
undermine trust in the whole analyzer) and "invalid property name" checks (MeliorMapper's API is
fully strongly-typed - ForMember(d => d.X, ...) - so there's no string-based property name to be
invalid in the first place).
Project layout
src/MeliorMapper.Abstractions Public contracts + Profile/CreateMap recording (no compilation)
src/MeliorMapper Core mapping engine (expression-tree compiler, delegate cache,
ProjectTo/report generation)
src/MeliorMapper.DependencyInjection services.AddMeliorMapper(...)
src/MeliorMapper.Analyzers Roslyn analyzer (MELIORMAP001-005) + code fix
src/MeliorMapper.Generator Incremental Roslyn source generator (Native AOT/trim-friendly,
scoped subset - see "Source generator" above)
tests/MeliorMapper.Tests xUnit + FluentAssertions test suite (100+ tests)
tests/MeliorMapper.Analyzers.Tests Analyzer tests (direct Roslyn CompilationWithAnalyzers harness)
tests/MeliorMapper.Generator.Tests Generator tests (runs the real generator, emits and loads the
resulting assembly, invokes the generated methods)
tests/MeliorMapper.Benchmarks BenchmarkDotNet: MeliorMapper vs manual mapping vs AutoMapper vs Mapster
samples/ConsoleSample Minimal end-to-end usage sample (Profile, ForMember, ReverseMap)
samples/MinimalApiSample ASP.NET Core minimal API (DI integration, ProjectTo over an
in-memory IQueryable)
samples/WorkerServiceSample Background queue-processing worker (Profile, ForMember, DI)
samples/GeneratorSample [GenerateMapper] usage; the Native AOT publish test above ran
against this project
samples/BlazorSample Blazor Server app: DI-injected IMapper used from a Razor
component to map an internal entity to a view-facing DTO
docs/ Architecture, Migration, Performance, and Best Practices guides
Native AOT (honest finding)
MeliorMapper's runtime engine is not currently compatible with strict Native AOT publishing
(dotnet publish -p:PublishAot=true). This was empirically tested (samples/ConsoleSample,
win-x64, self-contained) rather than assumed, and it fails - not in one isolated spot, but
throughout the compiler: dozens of IL2026/IL2070/IL3050 trim/AOT-analyzer errors from
System.Reflection calls (GetProperties, GetConstructors, MakeGenericMethod,
MakeGenericType, Activator.CreateInstance) and from .NET's own System.Linq.Expressions APIs
(Expression.Lambda(...).Compile(), Expression.Property(...)), which the BCL itself marks
RequiresDynamicCode/RequiresUnreferencedCode.
This is an earlier over-claim being corrected: the package description and this README previously
said "Native AOT friendly," which isn't accurate for the runtime engine as built. It's fixed now.
AddMapsFromAssembly/AddMapsFromAssemblies specifically are additionally annotated with
[RequiresUnreferencedCode] (they reflect over an entire assembly to find Profile types, which
trimming can't reason about) so calling them at least surfaces a warning in a trimmed/AOT build.
Why this is architectural, not a quick fix: MeliorMapper's whole design is "reflect once at
configuration time, then compile and cache an expression tree" - and expression-tree compilation
itself requires the code generation Native AOT specifically forbids. Annotating every reflection
call would only silence the warnings, not change the underlying runtime risk (a MakeGenericMethod
call for a type combination the AOT compiler didn't see ahead of time can fail at runtime, not just
warn at publish time). Genuine Native AOT support needs a fundamentally different code path - see
the source generator below, which now exists and was tested against exactly this scenario.
Source generator (MeliorMapper.Generator)
A separate, narrower way to get mapping code, specifically built to answer the Native AOT gap
above: an incremental Roslyn source generator that emits plain, ordinary C# at compile time - no
reflection, no System.Linq.Expressions, nothing for a trim/AOT analyzer to object to. It is
not a replacement for the runtime CreateMap-based IMapper and uses a different, attribute-
driven API (a compile-time generator can't see a runtime configuration lambda, so CreateMap's
fluent API doesn't apply here):
[MeliorMapper.GenerateMapper]
public partial class OrderMapper
{
public partial OrderDto Map(Order source);
}
var mapper = new OrderMapper();
var dto = mapper.Map(order); // body generated at compile time
Declare a partial class with [GenerateMapper] and one partial method per mapping shaped like
partial TDestination MethodName(TSource source); the generator fills in the body.
What's supported: exact/case-insensitive property-name matching, class destinations with a
public parameterless constructor and public settable properties, nested object members (mapped
recursively, inline in the same generated method), List<T>/array/IEnumerable<T>-shaped
collection members, numeric widening/narrowing, and enum↔string conversion.
What's deliberately out of scope (each reports a compile-time diagnostic - MELIORMAPGEN001
through MELIORMAPGEN006 - rather than silently generating something wrong or incomplete):
ForMember-equivalent customization, ReverseMap, Profiles, records/constructor-only mapping
(no public parameterless constructor), dictionaries, and self-referencing type graphs (the
generator has no runtime circular-reference guard, so a type that maps to itself again would
recurse infinitely at compile time instead - it reports MELIORMAPGEN001 and leaves that method
ungenerated instead). Use the runtime CreateMap-based IMapper for anything in this list.
Verified, not just claimed: samples/GeneratorSample runs a generated mapper (nested objects,
a collection, an enum→string conversion) and produces correct output. More importantly,
dotnet publish -c Release -r win-x64 --self-contained -p:PublishAot=true against that sample
compiled and passed IL trim/AOT analysis with zero IL20xx/IL30xx errors or warnings - a
stark contrast to the runtime engine's dozens of failures documented above. (The publish only got
as far as the native-linking step on this particular machine, which lacks the MSVC C++ linker
toolchain Native AOT needs to produce the final executable - an environment gap, not a code one;
the part that actually proves code-level AOT compatibility, compilation plus trim analysis, passed
cleanly.)
Known limitations
ForMemberoverrides on destination members that are only settable via a constructor (a record's primary-constructor parameters) are recorded but have no effect - the override doesn't reroute into the constructor argument. Common mutable-property DTOs (includinginit/required/private setters) are unaffected.Include/IncludeBase/IncludeAllDerivedmerge configuration one level deep and do not dispatch to a runtime-derived type from a base-typed reference (no polymorphicMap<Animal>dispatch yet).ConditionandPreConditionare currently equivalent (both gate whether a member is assigned); AutoMapper's distinction between "before" and "after" value resolution isn't modeled yet.- Dynamic/dictionary-sourced mapping (
ExpandoObject,Dictionary<string, object>) is the one place that inspects a value's runtime type; nested collections inside a dynamic object are boxed as-is rather than recursively converted to nested lists of dictionaries. - Open-generic
CreateMapregistrations carry no member customization and aren't validated byAssertConfigurationIsValid. - Reference-identity preservation for a non-cyclic diamond share (the same instance reachable from two different, non-self-referencing branches of a graph) is no longer guaranteed unless the shared type is itself self-referential - see the Performance section below.
- No incremental Roslyn source generator yet (deferred rather than shipped half-working - see ROADMAP.md); no Blazor sample yet.
Roadmap
See ROADMAP.md for the full phase-by-phase breakdown, what's still open in Phase 4 (source generator, remaining samples, continued performance work), and ideas beyond Phase 4.
Further reading
- docs/Architecture.md - how the pieces fit together, why the project is
split into two assemblies, and how
ProjectTodiffers from the runtime engine. - docs/Migration.md - a side-by-side table for AutoMapper/Mapster users.
- docs/Performance.md - what's free, what costs something, and how to benchmark your own mappings.
- docs/BestPractices.md - configuration,
ForMember,ProjectTovsMap, and testing recommendations. - CONTRIBUTING.md - project layout and the engine's non-negotiable properties
(no reflection while mapping, thread safety, pure-expression-tree
ProjectTo).
Building, testing & benchmarking
dotnet build
dotnet test
dotnet run -c Release --project tests/MeliorMapper.Benchmarks
Performance status (honest numbers)
Benchmark: mapping a Customer (nested Address, a List<Order> of 10 items) to CustomerDto,
.NET 10, BenchmarkDotNet, [MemoryDiagnoser].
Before the optimization pass below:
| Method | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|
| ManualMapping | 163.6 ns | 1.00 | 752 B | 1.00 |
| Mapster | 166.4 ns | 1.06 | 680 B | 0.90 |
| AutoMapper | 273.8 ns | 1.75 | 872 B | 1.16 |
| MeliorMapper | 869.6 ns | 5.55 | 1744 B | 2.32 |
MeliorMapper was slower than AutoMapper and Mapster, and allocated over 2x manual mapping. Root cause:
every class-to-class map unconditionally paid for circular-reference protection (a
Dictionary<object,object> allocated per top-level Map call, plus a lookup before constructing
every nested object, even ones like Address/Order that can never cycle back to themselves),
and nested member conversions were routed through a ConcurrentDictionary<TypePair, Lazy<T>> lookup
on every call instead of a direct reference.
After two targeted fixes - (1) a compile-time structural reachability check
(TypeGraphAnalyzer.IsSelfReachable) that only enables the reference cache for type maps that can
actually reach themselves again (real self-references like Node/NodeDto's Parent/Children
keep full protection; everything else skips the allocation entirely), and (2) dropping the
Lazy<T> wrapper from the delegate caches (a plain ConcurrentDictionary<TypePair, MapDelegate>,
accepting rare benign duplicate compilation under first-access contention instead of paying a
volatility check on every call):
| Method | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|
| ManualMapping | 142.6 ns | 1.02 | 752 B | 1.00 |
| Mapster | 200.5 ns | 1.43 | 680 B | 0.90 |
| AutoMapper | 353.0 ns | 2.52 | 872 B | 1.16 |
| MeliorMapper | 456.4 ns | 3.26 | 752 B | 1.00 |
Mean time dropped ~47% (869.6 ns → 456.4 ns) and allocations now exactly match manual mapping (752 B, down from 1744 B) - the reference-cache allocation is gone for this non-self-referencing DTO shape. AutoMapper's own number moved between runs too (274 ns → 353 ns), which is ordinary benchmark noise at this scale on a shared machine - treat the ratios as directional, not exact.
A third fix removed the remaining overhead called out above: nested member/constructor-argument/
collection-element/dictionary-key-value conversions used to go through
engine.MapCore(...) at map time - a ConcurrentDictionary<TypePair, MapDelegate> lookup on every
single call, even though the delegate for that type pair was already known once the parent type map
finished compiling. CompilationSession (new) now resolves and embeds that nested delegate directly
as a compiled-in constant while the parent's expression tree is being built, so map-time nested
conversions are a direct delegate invocation, not a dictionary hit. Self-referencing type graphs
(Node/NodeDto's Parent/Children) are still compiled safely: if a type pair is already being
compiled further up the same call stack, CompilationSession embeds a one-time indirection through a
DelegateBox (a mutable cell filled in once the enclosing compile finishes) instead of recursing into
the compiler again and overflowing the stack.
| Method | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|
| Mapster | 115.1 ns | 0.89 | 680 B | 0.90 |
| ManualMapping | 129.6 ns | 1.00 | 752 B | 1.00 |
| MeliorMapper | 182.1 ns | 1.41 | 752 B | 1.00 |
| AutoMapper | 222.8 ns | 1.73 | 872 B | 1.16 |
Mean time dropped a further ~60% (456.4 ns → 182.1 ns) with allocations unchanged (still 752 B,
matching manual mapping exactly). MeliorMapper is now faster than AutoMapper and within reach of
Mapster/manual mapping - the remaining gap is inherent to the two mechanisms manual/Mapster-style
mapping doesn't pay for at all: ResolutionContext allocation per top-level Map call and the
delegate-Invoke indirection itself (a real function-pointer call per nested member, vs. Mapster's
fully inlined single expression tree for the whole graph).
One documented trade-off from the first fix above: reference-identity preservation for a non-cyclic diamond share (the same object instance reachable from two different, non-self-referencing branches of a graph) is no longer guaranteed unless the shared type is itself self-referential. Preventing actual stack overflows on real cycles (the correctness-critical case) still works exactly as before, and is covered by tests - only the narrower "same non-recursive object appears twice, are the two mapped copies still reference-equal" guarantee is now scoped to self-referencing types.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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 is compatible. 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. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on MeliorMapper:
| Package | Downloads |
|---|---|
|
MeliorMapper.DependencyInjection
Microsoft.Extensions.DependencyInjection integration for MeliorMapper. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 176 | 7/27/2026 |