Mapper
Current Model
NOF's mapper is intentionally explicit, while still exposing an ambient convenience facade.
IMapperperforms runtime mapping.ManualMapperis the default implementation registered byNOF.Infrastructure.MapperRegistrationstores one mapping delegate for one(source, destination, name)key.MapperRegistrylives under the builder-ownedRegistryand freezes on first materialization.Mapper.Currentis ambient per async flow, not a process-wide mutable singleton.Mapper.PushCurrent(...)andMapperAmbientDaemonServiceestablish ambient mapper scope where convenience APIs need it.source.Mapuses the ambient mapper;source.MapWith(mapper)is the explicit alternative.
Core Types
public delegate object MapFunc(object source, IMapper mapper);
public sealed record MapKey(Type Source, Type Destination, string? Name = null);
public sealed record MapperRegistration(MapKey Key, MapFunc MappingFunc)
{
public static MapperRegistration Of<TSource, TDestination>(Func<TSource, TDestination> mappingFunc, string? name = null);
public static MapperRegistration Of<TSource, TDestination>(Func<TSource, IMapper, TDestination> mappingFunc, string? name = null);
}
public interface IMapper
{
TDestination Map<TSource, TDestination>(TSource source, bool useRuntimeType = false, string? name = null);
bool TryMap<TSource, TDestination>(TSource source, out TDestination result, bool useRuntimeType = false, string? name = null);
object Map(Type sourceType, Type destinationType, object source, string? name = null);
bool TryMap(Type sourceType, Type destinationType, object source, out object? result, string? name = null);
}
Source-Generated Registration
You declare mapping pairs on a partial static class using [Mappable]:
[Mappable<Order, OrderDto>]
[Mappable<Order, OrderSummary>(TwoWay = true)]
public static partial class Mappings;
The source generator emits an assembly initializer that adds MapperRegistration entries into Registry.MapperRegistry.
Those registrations become active when the assembly is loaded through AddApplicationPart(...).
No extra mapper bootstrap code is required in Program.cs.
Ambient vs Explicit Usage
Both styles are supported:
var dto = order.Map.To<OrderDto>();
var dto2 = order.MapWith(mapper).To<OrderDto>();
Use the ambient path for ergonomics inside NOF-managed scopes.
Use MapWith(...) when you want the dependency to remain explicit.
Runtime Resolution Order
ManualMapper resolves mappings in this order:
- exact source and destination type pair
- open generic source
- open generic destination
- open generic source plus open generic destination
- nullable destination fallback (
A -> T?can reuseA -> T)
The fallback only widens the destination. A mapping registered as A -> T? does not satisfy A -> T.
Generated Matching Rules
The [Mappable] source generator follows a small, explicit rule set:
- match public properties by name, case-insensitively
- choose the public constructor with the most matched parameters
- use implicit conversions when C# already supports them
- use explicit conversion operators when available
- unwrap
Optional<T>andResult<T>only when nullable semantics are safe - support
IValueObject<T>wrapping and unwrapping - support
Nullable<T>andIEnumerable<T>element conversion recursively - fall back to
IMapperwhen no direct codegen rule applies
Diagnostics
Current mapping diagnostics include:
NOF020: duplicate mapping registrationNOF021:[Mappable]target must bepartial staticNOF022: nullable semantic mismatch during wrapper unwrapNOF023: generated code falls back to anIMappermapping that is not auto-generated