Mappa.Generator
10.2.0
dotnet add package Mappa.Generator --version 10.2.0
NuGet\Install-Package Mappa.Generator -Version 10.2.0
<PackageReference Include="Mappa.Generator" Version="10.2.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Mappa.Generator" Version="10.2.0" />
<PackageReference Include="Mappa.Generator"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Mappa.Generator --version 10.2.0
#r "nuget: Mappa.Generator, 10.2.0"
#:package Mappa.Generator@10.2.0
#addin nuget:?package=Mappa.Generator&version=10.2.0
#tool nuget:?package=Mappa.Generator&version=10.2.0
Mappa.Generator
This source generator generates code for partial methods in partial classes tagged with the [Mappa] attribute defined in the Mappa package.
The generated code allow to map from a source type to a target type.
Assuming you have a partial method like the following
[Mappa]
public partial class Mapper
{
public partial TTarget Map(TSource input);
}
where TSource is the source type of the mapping and TTarget is the target type of the mapping, the source generator works by applying the strategies described below (see TypeMapIdentifierAlgorithm.cs).
Incremental discovery
Both the mapper and dependency-injection pipelines use Roslyn ForAttributeWithMetadataName so only types that declare [Mappa] or [MappaDependencyInjection] are considered. Each provider predicate also requires a partial ClassDeclarationSyntax. Non-partial attributed classes are ignored by the syntax provider (no generation and no provider-driven diagnostics). See Documentation/mappa-generator-algorithm.md β Incremental discovery and CancellationToken convention.
Dependency injection registration
Partial classes marked with [MappaDependencyInjection] are handled by a second incremental pipeline that emits an IServiceCollection registration method for [Mappa] mapper types in the registrar's assembly and, when InjectFromAssemblies is set, in those marker assemblies as well. Non-partial registrars never enter this pipeline. See Documentation/mappa-generator-algorithm.md β Dependency injection registration and Mappa attributes β MappaDependencyInjection.
The methods that can be auto-generated by πΊοΈ Mappa need to satisfy the following:
- The class they belong to needs to be marked with the
[Mappa]attribute; - The class they belong to needs to be partial;
- The method needs to be partial;
- The method must return a non-
voidtype; - The method must not return a
Task<T>(i.e. mapper methods cannot beasync); - The method must have one or two parameters; if the method has two parameters, the second must be of type
Mappa.MappaContext; - Methods marked with
[MappaIgnore]are excluded from mapping resolution; - Non-partial methods with an existing implementation can be registered as pre-mapped (used when resolving nested mappings);
- Methods from the mapper base-class hierarchy are collected automatically;
- Parameter ref modifiers must be absent or
in; any other ref modifier causes the method to be rejected.
Strategy resolution overview
Resolution follows one of two paths depending on whether the mapping is the root partial method or a nested type mapping (collection elements, dictionary keys/values, tuple items, constructor parameters, or property initializers).
Root partial methods
Root partial methods use TypeMapIdentifierAlgorithm, which runs the detector chain described below. Root methods do not invoke an existing map method for themselves.
Nested type mappings
Nested mappings use TypeMapIdentifierWithMapMethodAlgorithm, which runs an existing-method pre-step first (see below). If no existing method applies, the same detector chain is used.
Existing-method pre-step (nested mappings only)
Before the detector chain, TypeMapIdentifierWithMapMethodAlgorithm checks whether a suitable map method already exists:
- When:
- A method in the same class or a base class of the mapper from
TSourcetoTTargetexists OR, - A method from a property or field marked with the
[MappaDependency]on the mapper or an accessible base class of the mapper, or from a base class of the dependency type fromTSourcetoTTargetexists OR, - A method from a type defined via the
[MappaStaticDependency]attribute exists OR, - A polymorphic method where one of the
[MappaTypeMapping]attributes matchesTSourceandTTargetOR, - Setting
PolymorphicMapMethodWithMatchingDefaultAttributeisEnableand a polymorphic method with attribute[MappaTypeMappingDefault]and behaviourMappaTypeMappingDefaultBehavior.MapSourceType, whereTTargetmatches the target type defined by the attribute andTSourcematches the source type of the method, exists OR, - Setting
CompatibleMapMethodisEnableand a method exists whose parameter type is a base type or interface ofTSourceand whose return type is a derived type ofTTarget(selected after exact and nullability-relaxed matches fail);
- A method in the same class or a base class of the mapper from
- What:
- The existing method is invoked;
- Notes:
- A nested mapper method that requires
MappaContextis only invoked when the caller provides context (the root map method has aMappaContextparameter); - This pre-step is used when mapping elements of an array, keys or values of dictionaries, elements of tuples, and properties or constructor parameters of class/struct/record types;
- When
#nullable enableis active, nullability annotations must match exactly first. If no exact match exists, the generator may reuse a method with a relaxed nullability signature on the same underlying types: nested mapping toTTarget?may invoke a method returningTTarget, and nested mapping fromTSourcemay invoke a method acceptingTSource?.
- A nested mapper method that requires
Detector chain order
When no existing method applies (or for root methods), TypeMapIdentifierAlgorithm tries the following strategies in order:
- Identity (skipped when the root method has one or more
[MappaTypeMapping]attributes) - Nullable
- Polymorphic (only at the root, or immediately after nullable unwrapping β not at every nested level)
- Enum
- String
- Date & time
7a. IQueryable projection (root methods with
IQueryable<TSource>βIQueryable<TTarget>only) - Container
- Tuple
- Guid
- Constructor
Every GetStrategy discovery is wrapped with compile-time depth tracking (MaxCompileTimeDepth, default 50) and source/target type-pair cycle detection (MP00076 / MP00077). When BreakCompileTimeCycles is enabled, cycle edges synthesize private map methods and report MP00078 instead of MP00077. Details: Documentation/mappa-generator-algorithm.md β Reference handling and compile-time guards.
Reference handling
When ReferenceReusing is enabled and the map method declares MappaContext, generated code caches MappaReferenceManager in one local per map method (via an UnsafeAccessor getter), looks up pairs with TryGetReference<TTarget> (key: source identity + typeof(TTarget)), and registers with AddReferencePair<TTarget, TSource> early after construction so cycles can resolve. A redundant exit AddReferencePair is omitted when early registration already ran or a reusing nested map method already registered. MaxRuntimeDepth (short; effective default 0 = unlimited) limits nested reference-type mapping depth at runtime and throws MappaException when exceeded. Missing MappaContext warns with MP00074 / MP00075 and skips runtime reference handling. ReferenceReusing / MaxRuntimeDepth are rejected on IQueryable projection methods (MP00057).
Strategy details
1. Identity strategy
- When:
TSourceandTTargetare the same type (e.g.TSource => intandTTarget => int) OR,TSourcecan be implicitly converted intoTTarget(e.g.TSource => intandTTarget => long) OR,TSourcemaps toobjectorobject?(nullable-context rules apply: in#nullable enable,Tmaps toobject?; in#nullable disable,Tmaps toobject; when the source is not explicitly non-nullable annotated,Tmay map to non-nullableobject);
- What:
- By default (
ShallowCopy), the input value is assigned to the target; - When
IdentityMapDeepCopyisDeepCopyon a reference-type same-type root, the mapper clones the instance viaMappa.MappaCloning.MemberwiseClone(nested references remain shared); - When
IdentityMapDeepCopyisNestedDeepCopyon a reference-type root, the mapper clones the instance and recursively maps every accessible instance field; on a struct root, the struct is copied and fields are mapped recursively (noMemberwiseCloneon the struct itself); - Primitives, enums, and
stringsame-type mappings always assign and ignoreIdentityMapDeepCopy;
- By default (
- Notes:
DateTimeβDateTimeOffsetis handled by the identity strategy (implicit conversion);- The identity detector is skipped when the root method has
[MappaTypeMapping]attributes so that polymorphic mapping can run instead; - Same-type constructor-parameter identity in the constructor detector always uses shallow pass-through;
IdentityMapDeepCopyapplies only when the identity strategy is selected directly; NestedDeepCopyon array or collection same-type roots falls through to the container strategy so elements are mapped;- Nested field resolution disables the identity detector to avoid infinite recursion.
2. Nullable strategy
- When:
TSourceis theNullable<T>value type (e.g.int?) OR,TSourceis a nullable reference type when#nullable enable(e.g.string?) OR,TSourceis a reference type when#nullable disable(e.g.stringβ treated as a nullable source, consistent withIsReferenceNullable) OR,TTargetis a nullable value type andTSourceis the corresponding non-nullable value type,- AND a mapping exists from source to target when nullability is stripped (or, for nullable target / non-nullable source, a mapping exists for the inner types);
- What:
- When the source is nullable: if
TTargetcan benull, the mapper returnsnull; ifTTargetcannot benull, the mapper throws aNullReferenceException; - When the target is nullable and the source is non-nullable: the inner type is mapped without a nullable wrapper;
- When the source is nullable: if
- Notes:
- After nullable unwrapping, the polymorphic detector may run if the root method has
[MappaTypeMapping]attributes.
- After nullable unwrapping, the polymorphic detector may run if the root method has
3. Polymorphic mapping
- When:
- The root method has one or multiple
[MappaTypeMapping]attributes, and the polymorphic detector is allowed to run (at the root, or immediately after the nullable detector);
- The root method has one or multiple
- What:
- The runtime type of the input determines how the mapping from
TSourcetoTTargethappens; - For every
[MappaTypeMapping]attribute a mapping is created; - The default mapping when the actual parameter type does not match any source type from the
[MappaTypeMapping]attributes is determined by the[MappaTypeMappingDefault]attribute β if the attribute is not present, the default behaviour throws an exception;
- The runtime type of the input determines how the mapping from
- Notes:
- The polymorphic detector does not run at every nested level; it runs only at the root or immediately after nullable unwrapping.
4. Enum strategy
- When:
TSourceis anenumandTTargetis a differentenum, an integral numeric type compatible with the enum, or astringOR,TSourceis an integral numeric type compatible with the enum andTTargetis anenumOR,TSourceis astringandTTargetis anenum;
- What:
- Enum β enum and enum β string: a
switchstatement maps by shared enum member names, by shared underlying numeric values whenEnumToEnumMapSettingisNumericValue, or by shared[Description]attribute values whenEnumStringMapSettingorEnumToEnumMapSettingisDescription; - When mapping enum β enum, if at least one source enum member cannot be paired with a target member (by name, numeric value, or Description, depending on
EnumToEnumMapSetting), the generator reports warning MP00039 and continues code generation; unmapped source values throwArgumentOutOfRangeExceptionat runtime; - When Description mapping is enabled, every enum member involved must have a non-empty
[Description]attribute; otherwise the generator reports error MP00040 and does not generate the mapping; - When Description mapping or case-insensitive member-name mapping would pair multiple source members with the same target member, the generator reports error MP00041 and does not generate the mapping;
- Enum β integral: maps enum members to their underlying integral values;
- Integral β enum: maps integral values (cast to the enum underlying type) to enum members;
- String β enum: maps string input to enum members by name or by
[Description]value (handled by the enum strategy, not the string strategy, because the enum detector runs first); whenCaseInsensitiveEnumMapis enabled inMappaSettings, member names or Description values are matched case-insensitively viaToUpperInvariant(); [MappaMapEnumMember],[MappaMapEnumIgnore], and[MappaMapEnumDefault]on the map method are merged with settings-based pairing byEnumMapConfigurationResolver; explicit member overrides take precedence, ignored members are omitted, and unmapped values use the configured default arm (ThroworUseDefaultValue);
- Enum β enum and enum β string: a
- Notes:
- The enum detector runs before the general string strategy, so
stringβenumis handled here rather than by the string strategy; - Enum β enum matching uses member names by default; set
EnumToEnumMapSettingtoNumericValueto match by underlying numeric value, or toDescriptionto match by[Description]attribute value; - Enum β string matching uses member names by default; set
EnumStringMapSettingtoDescriptionto match by[Description]attribute value; - String β enum and enum β enum member-name matching is case-sensitive by default; enable
CaseInsensitiveEnumMapfor case-insensitive member name or Description matching; - Warning MP00039 is suppressed for members excluded via
[MappaMapEnumIgnore]and when[MappaMapEnumDefault]is configured withUseDefaultValue; - Errors MP00047βMP00054 halt mapping generation when enum configuration attribute validation fails (see enum mapping configuration attributes).
- The enum detector runs before the general string strategy, so
5. String strategy
- When:
TSourceis astringandTTargetis any of the numeric types OR,TSourceis astringandTTargetis any of the following types:DateTime,DateTimeOffset,DateOnly,TimeOnly,TimeSpan,Guid,UriOR,TTargetis astringOR,TSourceis astringandTTargetis any type with an accessible staticParse(string)method;
- What:
TSourceis astringandTTargetis any of the numeric types: the relevantParsemethod is invoked, possibly with the culture and NumberStyles fromMappaSettings;- When parsing numeric types from
string, if a style is configured the generator emits the BCL overload that acceptsNumberStyles:Parse(input, style)when culture is not configured, orParse(input, style, culture)when culture is configured. The resolved style is the type-specificNumberStyleswhen set, otherwiseGlobalNumberStyle. When neither is configured, the overload withoutNumberStylesis used; TSourceis astringandTTargetisDateTime,DateTimeOffset,DateOnly,TimeOnly,TimeSpan, orGuid: the relevantParseorParseExactmethod is used, possibly with format, culture, and DateTimeStyles fromMappaSettings;- When parsing
DateTime,DateTimeOffset,DateOnly, orTimeOnlyfromstring, if a style is configured the generator emits the BCL overload that acceptsDateTimeStylesas the last parameter:Parse(input, culture, style)orParseExact(input, format, culture, style). The resolved style is the type-specificDateTimeStyleswhen set, otherwiseGlobalDateTimeStyle. When culture is not configured,nullis passed forIFormatProvider. When neither type-specific nor global style is configured, the overload withoutDateTimeStylesis used; - When a
DateTimeStylesorNumberStylesvalue from[MappaSettings]contains undefined bits (for example an arbitrary integer cast such as(DateTimeStyles)999), the generator reports warning MP00038 and continues code generation using only the recognized flag bits in the emitted parse call. Values from.editorconfigthat cannot be parsed are ignored without a diagnostic; TSourceis astringandTTargetisUri:System.UriBuilderis used;TTargetis astringandTSourceisDateTime,DateTimeOffset,DateOnly,TimeOnly,TimeSpan, orGuid: the relevantToString()overload is used, possibly with format and culture fromMappaSettings;TTargetis astringandTSourceis any numeric type: the relevantToStringoverload is used, possibly with format and culture fromMappaSettings;TTargetis astring:TSource.ToString()is used;TSourceis astringandTTargethas an accessible staticParse(string)method: theParsemethod is invoked;
- Notes:
stringβenumis handled by the enum strategy, not the string strategy;- When an accessible static
Parse(string)exists, the string/Parsestrategy runs before the constructor strategy; a single-string-parameter constructor does not take priority over staticParse.
6. Date & time strategy
- When:
TSourceis aDateTimeandTTargetislong,DateOnly, orTimeOnlyOR,TSourceis aDateTimeOffsetandTTargetislong,DateOnly,TimeOnly, orDateTimeOR,TSourceis aDateOnlyandTTargetislongorDateTimeOR,TSourceis alongor any smaller numeric type implicitly castable tolong, andTTargetisDateTimeorDateTimeOffsetOR,TSourceis aTimeSpanandTTargetisdoubleOR,TSourceis adoubleandTTargetisTimeSpan;
- What:
- The usual mapping conversions are used;
- When mapping from
DateOnlytoDateTime,TimeOnly.MinValueis combined withDateTimeKind.Utc; - When mapping to or from
long, Unix time is used; - When a timezone is required, UTC is implied;
- Notes:
DateTimeβDateTimeis handled by the identity strategy, not the date & time strategy;DateTimeβDateTimeOffsetis handled by the identity strategy;DateTimeOffsetβDateTimeis handled by the date & time strategy (not identity);- There is no dedicated
DateOnlyβDateTimeOffsetstrategy in the current implementation (onlyDateOnlyβDateTime).
7a. IQueryable projection strategy
- When:
- The root map method source type implements or is
IQueryable<TSource>, - The root map method target type implements or is
IQueryable<TTarget>, TSourceandTTargetare different types,- An element mapping from
TSourcetoTTargetcan be expressed as a projection-compatible expression (constructor/property mapping, identity, nullable, enums, and common primitive/date conversions), - The root method does not declare
MappaBeforeMap/MappaAfterMaphooks or aMappaContextparameter;
- The root map method source type implements or is
- What:
- The generator emits
return source.Select(element => /* projection expression */);, - Element mapping is validated by
ProjectionCapabilityAnalyzerand built byProjectionExpressionBuilder(expression-tree-compatible code, not imperative statements),
- The generator emits
- Notes:
- Projection is signature-driven; no dedicated projection attribute is required,
- Mapping attributes such as
[MappaUseProperty]on the projection method participate in element construction, - Nested
IQueryableproperties, polymorphic root element maps, non-inlinable invoke methods, and before/after hooks are rejected with dedicated diagnostics (MP00055βMP00060), [MappaObjectFactory]on a projection map for the element target is rejected with MP00064,[MappaAllowInaccessibleSourceMembers]/[MappaAllowInaccessibleTargetMembers]on a projection map are rejected with MP00069,ReferenceReusing/MaxRuntimeDepthon a projection map are rejected with MP00057,- Mapping
IQueryable<TSource>to a concrete collection (for exampleList<TTarget>) is not a projection: the container path applies and may emit warning MP00061, - Prefer numeric or description enum mappings over case-insensitive member-name matching for ORM providers (warning MP00060),
- Generated projection methods are annotated with
[RequiresDynamicCode]and are not compatible with Native AOT deployment.
7. Container strategy
- When:
TSourceandTTargetare both either dictionaries or collections,- For dictionaries, mappings exist from source key type to target key type and from source value type to target value type,
- For collections, a mapping exists from the source element type to the target element type,
- Neither side is an
IQueryable<T>participating in a projection pair (queryable sources/targets are excluded from eager collection materialization when projection applies; see Β§7a), TSourcedictionary types accepted:- any type implementing
IDictionary<TKey, TValue>; - any type implementing
IReadOnlyDictionary<TKey, TValue>; - any type implementing
IEnumerable<KeyValuePair<TKey, TValue>>;
- any type implementing
TTargetdictionary types accepted:- any type implementing
IDictionary<TKey, TValue>that has a constructor with zero arguments; - the following interfaces:
IDictionary<TKey, TValue>,IReadOnlyDictionary<TKey, TValue>,IImmutableDictionary<TKey, TValue>; - the following classes:
ImmutableDictionary<TKey, TValue>,ImmutableSortedDictionary<TKey, TValue>,FrozenDictionary<TKey, TValue>,ConcurrentDictionary<TKey, TValue>;
- any type implementing
TSourcecollection types accepted: any type implementingIEnumerable<T>, arrays,Span<T>,ReadOnlySpan<T>,Memory<T>, andReadOnlyMemory<T>;TTargetcollection types accepted:- any type implementing
ICollection<T>orISet<T>that has a constructor with zero arguments (or one constructor with one integer argument of typeintwhenMappaSettings.ContainerCapacityConstructorsis enabled); - any type derived from
Stack<T>,Queue<T>, orBlockingCollection<T>that has a constructor with zero arguments (or one constructor with one integer argument whenMappaSettings.ContainerCapacityConstructorsis enabled); - the following interfaces:
IEnumerable<T>,ICollection<T>,IReadOnlyCollection<T>,ISet<T>,IList<T>,IReadOnlyList<T>,IReadOnlySet<T>,IImmutableSet<T>,IImmutableList<T>,IImmutableQueue<T>,IImmutableStack<T>,IProducerConsumerCollection<T>; - the following classes: arrays,
List<T>,ReadOnlyCollection<T>,Span<T>,ReadOnlySpan<T>,Memory<T>,ReadOnlyMemory<T>,Stack<T>,Queue<T>,ReadOnlySet<T>,HashSet<T>,SortedSet<T>,FrozenSet<T>,ImmutableHashSet<T>,ImmutableSortedSet<T>,ImmutableArray<T>,ImmutableList<T>,ImmutableQueue<T>,ImmutableStack<T>,ConcurrentBag<T>,ConcurrentQueue<T>,ConcurrentStack<T>;
- any type implementing
- What:
- A
forloop orforeachloop is added to the code; - In the loop, each element from the source collection is mapped to an element of the target collection and then added to the target collection;
- A
- Notes:
- When possible for some types (e.g.
List<T>), the constructor accepting capacity is preferred to reduce allocations (ContainerCapacityConstructors); - When
MappaSettings.FastCollections(or the corresponding.editorconfigsetting) is enabled, collection mapping may use span-based iteration for array andList<T>sources and may allocate target arrays with a known capacity; - When
MappaSettings.PreventEnumerableCount(or the corresponding.editorconfigsetting) is enabled, mapping from a source withoutCountorLengthto a fixed-size target such asT[],Span<T>,ReadOnlySpan<T>,Memory<T>, orReadOnlyMemory<T>uses a growableList<T>buffer instead of callingEnumerable.Count, so the source is enumerated only once; - When
MappaSettings.EnumerableConcreteTypeisArray(or the corresponding.editorconfigsetting), sequence-like interface targets such asIEnumerable<T>,IList<T>,ICollection<T>,IReadOnlyList<T>, andIReadOnlyCollection<T>use aT[]buffer instead ofList<T>; concreteList<T>targets are unchanged; - When
MappaSettings.DictionaryAssignmentisAdd(or the corresponding.editorconfigsetting), dictionary-to-dictionary mappings insert entries withIDictionary<TKey, TValue>.Add(key, value)instead of the indexer; explicitIDictionary<K, V>implementations use a typed interface temporary when required; the default is indexer assignment; - Explicit interface implementation is supported;
- Types with an empty constructor are supported if they are derived from any of the supported interfaces or classes.
- When possible for some types (e.g.
8. Tuple strategy
- When:
TSourceandTTargetare both tuple types,- The number of elements in the tuple is the same,
- For each element of the tuple there exists a mapping from source element to target element;
- What:
- Each element of the source tuple is mapped into a new element of the target tuple;
- The target tuple is created by combining the mapped elements;
- Notes:
- Both named and unnamed tuples are supported;
- The type
Tuple<T>(and its variations with more elements) is supported.
9. Guid strategy
- When:
TSourceisGuidandTTargetisbyte[],Span<byte>,ReadOnlySpan<byte>,Memory<byte>, orReadOnlyMemory<byte>, ORTSourceisbyte[],Span<byte>,ReadOnlySpan<byte>,Memory<byte>, orReadOnlyMemory<byte>andTTargetisGuid;
- What:
- A mapping from
Guidtobyte[]/Span<byte>is defined using the relevantGuidconstructors or theGuid.ToByteArray()method.
- A mapping from
10. Constructor strategy
The constructor strategy has four sub-strategies, tried in order:
| Sub-strategy | When |
|---|---|
| Object factory | A [MappaObjectFactory] registration on the root map method (class βͺ method attributes) resolves to an accessible factory for the exact target type |
| Mapping constructor | An accessible constructor TTarget(TSource input) exists where the single parameter type matches the source type (or an inaccessible matching constructor when [MappaAllowInaccessibleTargetMembers] allows constructors) |
| Empty constructor + property init | An accessible zero-argument constructor exists; settable properties (and get-only collections per the notes below) can be mapped from the source (or an inaccessible empty constructor when allowed) |
| Parameterized constructor | No suitable empty-constructor path; the accessible constructor with the most mappable parameters is chosen (parameter names are matched to source properties case-insensitively by default) (inaccessible parameterized constructors participate when allowed) |
- What:
- Object factory: invoke the resolved factory; depending on signature tier, either return the factory result as-is, fill properties like the empty-constructor path after the call, or map factory parameters like the parameterized-constructor path;
- Otherwise each property, constructor argument, or mapping-constructor parameter is mapped and a new instance of
TTargetis generated withnew(or via a generatedUnsafeAccessorconstructor helper when the selected constructor is inaccessible); - Get-only dictionary or collection properties for which a mapper exists are filled with mapped values from the corresponding source;
- Notes:
- Object factories are keyed by exact target type and apply to nested mappings whose target type matches a registration on the root map method;
- Duplicate factories for the same target type report error MP00062; unresolved factories warn with MP00063 and fall through to constructor strategies; factories on
IQueryableprojection maps report error MP00064; - Factory location and signature tiers are documented in Mappa attributes β MappaObjectFactory;
- Empty-constructor path: property name matching is case-sensitive by default, configurable via
CaseInsensitivePropertyMapandIgnoreUnderscoreForPropertyMapinMappaSettings; - Parameterized-constructor path: constructor parameter names are matched to source properties case-insensitively by default, with optional underscore-insensitive matching via
IgnoreUnderscoreForPropertyMap; - Explicit interface implementation is supported for get-only dictionary and collection properties;
- Get-only
Stack<T>,Queue<T>,ConcurrentStack<T>,ConcurrentQueue<T>,ConcurrentBag<T>, andBlockingCollection<T>properties are filled post-construction usingPush,Enqueue, orAddrespectively; - The following attributes on the map method can override mapping behaviour (see
Documentation/mappa-attributes.mdfor details):[MappaUseProperty][MappaIgnoreTargetProperty](empty-constructor / empty-ctor-like factory path only)[MappaMustMapTargetProperty](empty-constructor / empty-ctor-like factory path only; flat property names; unmapped must-map properties report MP00065)[MappaAllowInaccessibleSourceMembers](opt-in private/protected source property reads viaUnsafeAccessor; flat names or all; MP00067 / MP00069)[MappaAllowInaccessibleTargetMembers](opt-in private/protected target property writes and/or inaccessible constructors viaUnsafeAccessor;AllowProperties/AllowConstructors; MP00067βMP00069)[MappaAssignFromContext][MappaAssignFromConstant][MappaInvokeMethod]β optionally acceptsSourcePropertyNameto select the source property passed to the invoked method[MappaAssignToContext](post-construction context writes; requires the caller to provideMappaContext)
- Those attributes accept flat names or dot-separated nested property paths (see also algorithm β nested property paths) (except the inaccessible-member attributes, which accept flat names only);
- When inaccessible setters or unsafe-accessor constructors are used, property assignments that would normally use an object initializer are emitted after construction; helpers are generated in a file-local
__MappaInaccessibleAccessorstype in the mapper's namespace; - When
MappaSettings.ProtobufOptionalis enabled, optional protobuf members are handled via companionHas*properties on the source and target types.
Nested property paths
Leaf source chains use conditional access based on each receiver type, reuse nested source temps, and append ?? throw new System.NullReferenceException("...") only when the expression can be null and the target cannot (never on plain non-nullable value-type leaves). Nested [MappaIgnoreTargetProperty] applies alongside sibling nested attributes under the same root. Nested [MappaAssignToContext] reads the leaf from the constructed result after mapping. Details: Documentation/mappa-generator-algorithm.md β Nested property paths.
Before and after map hooks
Root mapping methods may declare [MappaBeforeMap] and [MappaAfterMap] on the mapper class and/or method. Hooks apply only to generated root methods. Resolution prefers ref T + context, then ref T, then context, then parameterless (T is source for before hooks and target for after hooks). Before hooks run class-then-method; after hooks run method-then-class. Unresolved hooks warn with MP00045; class/method duplicates warn with MP00046 and invoke once. After hooks force materialization of the core mapping result into a local before the after-hook calls and return. Details: Documentation/mappa-generator-algorithm.md β Before and after map hooks.
Limitations
Currently unsupported features include:
- Generic type parameters on map methods (for example,
TTarget Map<TSource, TTarget>(TSource input)). - Nested
IQueryable/ collection projections inside member initializers (for example projectingsource.ChildrenasIQueryableinside a parent projection). - Tuple and
Guidexpression builders inside queryable projections. - Provider-specific expression shapes that compile but are not translated by a given LINQ provider at runtime.
ReferenceReusing/MaxRuntimeDepth(andMappaContext) onIQueryableprojection map methods.
Other relevant packages:
- Mappa: attributes and
MappaContextused to drive the source generator; - Mappa Protobuf: methods to map
Google.Protobuf.WellKnownTypesobjects from Google.Protobuf package into common objects. - Mappa Protobuf dependency: utility methods to register the Protobuf mapper.
- Mappa Bson: methods to map
MongoDB.Bsonobjects from MongoDB.Bson package into common objects. - Mappa Bson dependency: utility methods to register the Bson mapper.
You can find samples here. Visit the Mappa documentation to learn more.
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.