MappShark 2.1.1

dotnet add package MappShark --version 2.1.1
                    
NuGet\Install-Package MappShark -Version 2.1.1
                    
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="MappShark" Version="2.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MappShark" Version="2.1.1" />
                    
Directory.Packages.props
<PackageReference Include="MappShark" />
                    
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 MappShark --version 2.1.1
                    
#r "nuget: MappShark, 2.1.1"
                    
#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 MappShark@2.1.1
                    
#: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=MappShark&version=2.1.1
                    
Install as a Cake Addin
#tool nuget:?package=MappShark&version=2.1.1
                    
Install as a Cake Tool

MappShark

Zero-overhead object mapping for .NET — powered by a Roslyn source generator.

MappShark generates plain C# mapping code at compile time. There is no reflection on the hot path, no warm-up cost, and no runtime configuration exceptions — mapping errors become build errors.

NuGet License: MIT


Library Comparison

The table below scores MappShark against the most popular .NET object-mapping libraries across technical criteria. Documentation quality and community size are intentionally excluded — MappShark is a newer library and a direct comparison on those axes would not be meaningful.

Scores are out of 100. Higher is better.

Criterion AutoMapper Mapster Mapperly MappShark
Runtime performance ¹ 50 89 96 94
Startup / warm-up overhead 30 55 95 90
Compile-time safety 15 20 80 90
Native AOT / trimming support 15 40 95 90
Configuration simplicity 50 65 60 75
Name-based automatic mapping 90 85 80 75
Custom type converters 90 80 65 75
Collection & dictionary mapping 85 80 70 80
IQueryable / EF Core projections 85 80 25 75
Records & init-only support 55 65 85 90

¹ Score derived from real BenchmarkDotNet results (net8.0, .NET 8.0.27, Intel i5-4430S): ManualMapping 427 ns · MappShark 456 ns (1.07×) · Mapperly 443 ns (1.04×) · Mapster 483 ns (1.13×) · AutoMapper 854 ns (2.00×)

Key takeaways:

  • AutoMapper and Mapster are mature and flexible but rely heavily on runtime reflection, making them incompatible with Native AOT and carrying significant startup costs.
  • Mapperly generates code at compile time and has excellent AOT support, but its IQueryable projection story is limited and its configuration model is more verbose.
  • MappShark combines compile-time code generation, build-time error reporting, Native AOT compatibility, and first-class EF Core projection support in a single lightweight package.

Table of Contents

  1. Installation
  2. Quick Start
  3. How It Works
  4. Mapping Strategies
  5. Records and Init-Only Properties
  6. Nested Objects
  7. Collections
  8. Dictionary Mapping
  9. Custom Value Converters
  10. Organizing Mappings with Profiles
  11. Custom Property Mappings with ForMember
  12. Reverse Mapping with BothWays
  13. IQueryable Projections (EF Core)
  14. Strict Generated Mode
  15. Build-Time Diagnostics
  16. API Reference
  17. Targets & Compatibility

Installation

dotnet add package MappShark

Requires .NET Standard 2.0 or .NET 8+. The source generator is included in the package — no additional setup is needed.


Quick Start

1. Annotate your types

using MappShark;

public class UserEntity
{
    [MapIndex(0)] public int Id { get; set; }
    [MapIndex(1)] public string FullName { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; } // no [MapIndex] → ignored
}

public class UserDto
{
    [MapIndex(0)] public int UserId { get; set; }    // mapped from Id   (index 0)
    [MapIndex(1)] public string Name { get; set; } = string.Empty; // mapped from FullName (index 1)
}

2. Call Mapper.Map

var dto = Mapper.Map<UserEntity, UserDto>(entity);
// dto.UserId == entity.Id
// dto.Name   == entity.FullName

The source generator produces an optimized static method during your build — no warm-up, no reflection, no surprises.


How It Works

When you write Mapper.Map<UserEntity, UserDto>(...), MappShark's Roslyn analyzer detects the call at compile time and emits a file called IndexedMapResolver.g.cs into your project. This file contains a plain C# method:

private static UserDto MapPair_0(UserEntity source)
{
    var destination = new UserDto();
    destination.UserId = source.Id;
    destination.Name   = source.FullName;
    return destination;
}

At runtime, Mapper.Map resolves to this method via a cached static delegate. Zero reflection. Zero allocations beyond the destination object itself.

For init-only properties and records, object-initializer or constructor-call syntax is emitted instead — the only valid way to assign those members after construction.


Mapping Strategies

1. Index-Based Mapping ([MapIndex])

Apply [MapIndex(n)] to source and destination properties. Any two properties sharing the same index are mapped to each other, regardless of their names.

public class OrderEntity
{
    [MapIndex(0)] public string Reference { get; set; } = string.Empty;
    [MapIndex(1)] public decimal TotalAmount { get; set; }
}

public class OrderDto
{
    [MapIndex(0)] public string Code { get; set; } = string.Empty;  // ← from Reference
    [MapIndex(1)] public decimal Total { get; set; }                // ← from TotalAmount
}

Available since v1.0.0

2. Name-Based Fallback

Properties that share the same name and a compatible type are mapped automatically — no annotation needed.

public class ProductEntity
{
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public int StockCount { get; set; }
}

public class ProductDto
{
    public string Name { get; set; } = string.Empty;  // ← mapped by name
    public decimal Price { get; set; }                // ← mapped by name
    // StockCount is absent → simply not mapped
}

var dto = Mapper.Map<ProductEntity, ProductDto>(entity);

[MapIndex] always takes priority. Name-based mapping only applies to properties that carry no index annotation on either side.

Available since v1.0.0

3. Name Override Attributes ([MapFrom] / [MapTo])

Use these attributes when source and destination properties have different names but you do not want to use [MapIndex] on both sides — for example, to keep domain models free of mapping concerns.

[MapFrom("SourcePropertyName")]

Apply to a destination property. Reads the value from the named source property.

public class OrderDto
{
    public int Id { get; set; }

    [MapFrom("TotalAmount")]
    public decimal Total { get; set; }
}

public class OrderEntity
{
    public int Id { get; set; }
    public decimal TotalAmount { get; set; } // no annotation needed
}

var dto = Mapper.Map<OrderEntity, OrderDto>(entity);
// dto.Total == entity.TotalAmount
[MapTo("DestinationPropertyName")]

Apply to a source property. Writes its value into the named destination property.

public class CreateOrderCommand
{
    public string Reference { get; set; } = string.Empty;

    [MapTo("Price")]
    public decimal Amount { get; set; }
}

public class OrderDto
{
    public string Reference { get; set; } = string.Empty;
    public decimal Price { get; set; } // no annotation needed
}

var dto = Mapper.Map<CreateOrderCommand, OrderDto>(command);
// dto.Price == command.Amount
Priority order

When multiple strategies could resolve the same destination property, MappShark uses the following priority:

  1. [MapIndex] — highest priority, always wins
  2. [MapFrom] on the destination property
  3. [MapTo] on the source property
  4. Same-name fallback — lowest priority

Note: [MapFrom] and [MapIndex] cannot be combined on the same property (diagnostic MSP015). [MapTo] and [MapIndex] cannot be combined either (MSP016).

Available since v1.0.0

Dot-Path Notation (nested property access)

[MapFrom] accepts a dot-separated path to read from a chain of nested properties — no profile or ForMember call needed. Arbitrary depth is supported.

public class PostEntity
{
    public int Id { get; set; }
    public AuthorEntity Author { get; set; } = new();
}

public class AuthorEntity
{
    public string UserName { get; set; } = string.Empty;
    public ContactInfo Contact { get; set; } = new();
}

public class ContactInfo
{
    public string Email { get; set; } = string.Empty;
}

public class PostDto
{
    public int Id { get; set; }

    [MapFrom("Author.UserName")]
    public string AuthorUserName { get; set; } = string.Empty;

    [MapFrom("Author.Contact.Email")]
    public string AuthorEmail { get; set; } = string.Empty;
}

var dto = Mapper.Map<PostEntity, PostDto>(entity);
// dto.AuthorUserName == entity.Author.UserName
// dto.AuthorEmail    == entity.Author.Contact.Email

The source generator emits the path verbatim into the produced code:

private static PostDto MapPair_0(PostEntity source) =>
    new PostDto
    {
        Id             = source.Id,
        AuthorUserName = source.Author.UserName,
        AuthorEmail    = source.Author.Contact.Email,
    };

Dot-paths also work on positional record parameters:

public sealed record PostDto(
    int Id,
    [MapFrom("Author.UserName")]       string AuthorUserName,
    [MapFrom("Author.Contact.Email")]  string AuthorEmail
);

The reflection fallback resolves the property chain at runtime, so dot-paths work even when the source generator is not active.

If any segment cannot be resolved to a readable public property on the preceding type, build error MSP017 is reported.

Available since v2.1.0


Records and Init-Only Properties

MappShark fully supports C# records and any class or struct with init-only setters — no extra configuration required.

Records with explicit properties

The source generator emits object-initializer syntax for destination types that have a public parameterless constructor and init-only setters (the default for record types with explicit properties):

public record OrderDto
{
    public int Id { get; init; }

    [MapFrom("TotalAmount")]
    public decimal Total { get; init; }

    [MapFrom("CustomerName")]
    public string Customer { get; init; } = string.Empty;
}

var dto = Mapper.Map<OrderEntity, OrderDto>(entity);

Generated code:

private static OrderDto MapPair_0(OrderEntity source) =>
    new OrderDto
    {
        Id = source.Id,
        Total = source.TotalAmount,
        Customer = source.CustomerName,
    };

Positional records

Positional records (record Foo(int Bar)) have no parameterless constructor. MappShark emits constructor-call syntax for them. All three mapping attributes — [MapIndex], [MapFrom], and [MapTo] — can be placed directly on positional parameters:

public record OrderDto(
    int Id,
    [MapFrom("TotalAmount")] decimal Total,
    [MapFrom("CustomerName")] string Customer
);

var dto = Mapper.Map<OrderEntity, OrderDto>(entity);

Generated code:

private static OrderDto MapPair_0(OrderEntity source) =>
    new OrderDto(
        Id: source.Id,
        Total: source.TotalAmount,
        Customer: source.CustomerName);

The [property: MapFrom("X")] target specifier is also accepted as an alternative syntax.

Limitation: Mapper.Projection (IQueryable expression trees) is not supported for positional records.

Orphan parameters

Positional parameters that have no matching source property are treated as orphans and silently skipped. Their constructor slot receives the CLR default (null / default(T)). This allows you to include extra parameters in the record that you populate yourself after mapping:

public sealed record RegisterUserResponseDto(
    [MapFrom("PublicId")]  Guid   UserId,
    [MapFrom("UserName")]  string UserName,
    [MapFrom("Email")]     string Email,
    bool VerificationRequired   // orphan → defaults to false; set it yourself
);

var dto = Mapper.Map<UserEntity, RegisterUserResponseDto>(entity);
// dto.VerificationRequired == false  ← set it afterward as needed

Available since v1.0.0. Orphan parameter fix in reflection fallback: v1.0.2.


Nested Objects

MappShark handles nested objects automatically. Call Mapper.Map once for the top-level type — the generator discovers and wires up all required nested pairs recursively.

public class OrderEntity
{
    [MapIndex(0)] public string OrderNumber { get; set; } = string.Empty;
    [MapIndex(1)] public CustomerEntity? Customer { get; set; }
    [MapIndex(2)] public List<OrderLineEntity>? Lines { get; set; }
}

public class OrderDto
{
    [MapIndex(0)] public string Code { get; set; } = string.Empty;
    [MapIndex(1)] public CustomerDto? Customer { get; set; }
    [MapIndex(2)] public List<OrderLineDto>? Items { get; set; }
}

// One call maps the entire object graph:
var dto = Mapper.Map<OrderEntity, OrderDto>(order);

The generator discovers CustomerEntity → CustomerDto and OrderLineEntity → OrderLineDto automatically and generates optimized code for each pair.

Available since v1.0.0


Collections

Supported destination collection types:

Type Supported
List<T>
IList<T>, ICollection<T>, IEnumerable<T>
T[] (arrays)
Dictionary<TKey, TValue>
IDictionary<TKey, TValue>
IReadOnlyDictionary<TKey, TValue>

Use Mapper.MapMany to map an entire flat collection in a single call:

IEnumerable<UserEntity> entities = GetUsers();
List<UserDto> dtos = Mapper.MapMany<UserEntity, UserDto>(entities);

Available since v1.0.0


Dictionary Mapping

MappShark maps Dictionary<TKey, TValue> properties end-to-end. Both keys and values are handled without any extra configuration.

Simple value types

public class PriceListEntity
{
    [MapIndex(0)] public Dictionary<string, decimal> Prices { get; set; } = new();
}

public class PriceListDto
{
    [MapIndex(0)] public Dictionary<string, decimal> Prices { get; set; } = new();
}

var dto = Mapper.Map<PriceListEntity, PriceListDto>(entity);
// dto.Prices["apple"] == entity.Prices["apple"]

Nested object values

When source and destination value types are themselves a mappable pair, MappShark generates a mapper for the value type automatically:

public class CatalogEntity
{
    [MapIndex(0)] public Dictionary<string, ProductEntity> Items { get; set; } = new();
}

public class CatalogDto
{
    [MapIndex(0)] public Dictionary<string, ProductDto> Items { get; set; } = new();
}

var dto = Mapper.Map<CatalogEntity, CatalogDto>(catalog);
// Each ProductEntity value is mapped to a ProductDto via a generated mapper.

The destination property can be declared as Dictionary<K,V>, IDictionary<K,V>, or IReadOnlyDictionary<K,V>. Any source type that implements one of those interfaces works as a source (e.g., SortedDictionary<K,V>).

Notes:

  • Keys must be directly assignable. Incompatible keys produce build error MSP004.
  • Dictionary properties are excluded from Mapper.Projection expressions — MSP013 warning is emitted.

Available since v1.0.0


Custom Value Converters

When a property needs a custom transformation (e.g., decimal → string), implement IMapValueConverter<TSource, TDestination> and attach it with [MapConverter].

public class PercentConverter : IMapValueConverter<decimal, string>
{
    public string Convert(decimal value) => $"{value * 100:0.##}%";
}

public class MetricDto
{
    [MapIndex(0)]
    [MapConverter(typeof(PercentConverter))]
    public string RatioLabel { get; set; } = string.Empty;
}

Requirements:

  • Must implement IMapValueConverter<TSourceMember, TDestinationMember>.
  • Must be a concrete, non-abstract class with a public parameterless constructor.

[MapConverter] on source properties

You can also place [MapConverter] on a source property (or positional record parameter). This keeps the destination type completely free of MappShark attributes:

public class MetricEntity
{
    [MapIndex(0)]
    [MapConverter(typeof(PercentConverter))]  // converter lives on the source side
    public decimal Ratio { get; set; }
}

public class MetricDto
{
    [MapIndex(0)]
    public string RatioLabel { get; set; } = string.Empty; // no MappShark attributes needed
}

[MapConverter] on destination properties: available since v1.0.0

[MapConverter] on source properties / record parameters: available since v1.1.0

Note: Properties with [MapConverter] are excluded from Mapper.Projection (IQueryable) expressions — diagnostic MSP013 is emitted.


Organizing Mappings with Profiles

For medium and large projects, use MappSharkProfile to group related mappings together. The source generator discovers your profiles automatically — no runtime registration needed.

using MappShark;

public class OrderMappingProfile : MappSharkProfile
{
    public OrderMappingProfile()
    {
        CreateMap<OrderEntity, OrderDto>();
        CreateMap<OrderDto, OrderEntity>();       // reverse direction
        CreateMap<OrderLineEntity, OrderLineDto>();
    }
}

Available since v1.0.0


Custom Property Mappings with ForMember

ForMember lets you define custom property mappings directly inside a MappSharkProfile, using a plain lambda expression as the resolver. This is the recommended approach for:

  • Nested path access — reading a property from a child object (src => src.User.UserName)
  • Computed / aggregate values — any expression that cannot be expressed as a direct property copy (src => src.PostVotes!.Count(v => v.IsRelevant))
using MappShark;

public class PostMappingProfile : MappSharkProfile
{
    public PostMappingProfile()
    {
        CreateMap<PostEntity, PostDto>()
            .ForMember(dto => dto.AuthorUserName,   src => src.User!.UserName)
            .ForMember(dto => dto.RelevantVotes,    src => src.PostVotes!.Count(v => v.IsRelevant));
    }
}

The resolver lambda body is emitted verbatim into the generated mapper method — there is no runtime delegate invocation overhead on the hot path.

Registering ForMember for the Reflection Fallback Path

When the Roslyn source generator is active (the default in any project that references the MappShark NuGet), ForMember mappings are generated at compile time. No registration call is needed.

If you are using MappShark without the source generator (e.g., in a dynamically loaded assembly), call Mapper.UseProfile<T>() at startup to register the ForMember delegates:

// Call once at application startup, before any mapping takes place.
Mapper.UseProfile<PostMappingProfile>();

Note: ForMember takes priority over name-based fallback. If a destination property is covered by both a ForMember override and a same-name auto-mapping, ForMember wins.

Note: Properties mapped via ForMember are excluded from Mapper.Projection() / Mapper.ProjectMany() — LINQ expression trees cannot translate arbitrary C# expressions to SQL.

Available since v2.0.0


Reverse Mapping with BothWays

Mapper.BothWays signals the source generator to produce mappers for both directionsA → B and B → A — from a single call.

// This call registers both UserEntity → UserDto and UserDto → UserEntity.
var dto = Mapper.BothWays<UserEntity, UserDto>(entity);

// Later, map back:
var entity = Mapper.Map<UserDto, UserEntity>(dto);

You only need one BothWays call anywhere in your codebase (e.g., in a profile or startup file) to register both directions.

Available since v1.0.0


IQueryable Projections (EF Core)

Mapper.Projection<TSource, TDestination>() returns a compile-time-generated Expression<Func<TSource, TDestination>> that you can pass directly to LINQ .Select(). This lets EF Core translate the projection to SQL and fetch only the columns you need.

List<UserDto> dtos = await dbContext.Users
    .Where(u => u.IsActive)
    .Select(Mapper.Projection<UserEntity, UserDto>())
    .ToListAsync();

No EF Core package is required by MappShark itself — System.Linq.Expressions is part of the standard library.

Note: Properties with [MapConverter] and dictionary properties are excluded from projections (MSP013). Positional records are not supported in projections.

Available since v1.0.0


Strict Generated Mode

By default, MappShark falls back to a reflection-based mapper when no generated mapper is found — useful during development or for types discovered at runtime. To enforce that only generated mappers are used, set the AppContext switch at startup:

// Program.cs or equivalent startup code:
AppContext.SetSwitch("MappShark.StrictGeneratedMode", true);

With strict mode enabled, Mapper.Map throws InvalidOperationException if no generated mapper exists for a given pair, making misconfiguration immediately visible.

Available since v1.0.0


Build-Time Diagnostics

MappShark surfaces configuration problems as compiler errors or warnings — they appear in your IDE and CI build output, never at runtime.

Code Severity Description
MSP001 Error Duplicate [MapIndex] on source type
MSP002 Error Duplicate [MapIndex] on destination type
MSP003 Error Destination index has no matching source index
MSP004 Error Source and destination indexed properties are type-incompatible
MSP005 Error Indexed source property has no public getter
MSP006 Error Indexed destination property has no public setter
MSP007 Error [MapIndex] on a static property
MSP008 Error Index value is negative
MSP009 Error Converter does not implement IMapValueConverter<,>
MSP010 Error Converter cannot be instantiated (abstract or no public parameterless constructor)
MSP011 Error Destination collection type is not supported
MSP012 Error Destination nested/element type has no public parameterless constructor
MSP013 Warning Property with [MapConverter] or dictionary is excluded from IQueryable projections
MSP014 Error [MapFrom] references a source property that does not exist
MSP015 Error [MapFrom] and [MapIndex] used on the same property
MSP016 Error [MapTo] and [MapIndex] used on the same property
MSP017 Error A segment of a [MapFrom] dot-path cannot be resolved to a readable public property on the preceding type

API Reference

Method Description
Mapper.Map<TSource, TDest>(source) Maps a single object.
Mapper.MapMany<TSource, TDest>(source) Maps a collection to List<TDest>.
Mapper.BothWays<TSource, TDest>(source) Maps forward and registers the reverse pair for code generation.
Mapper.Projection<TSource, TDest>() Returns a compile-time-generated expression for IQueryable.Select().

Targets & Compatibility

Framework Supported
.NET Standard 2.0
.NET 8
.NET 9+
Native AOT
Blazor / MAUI

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.
  • net8.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.

Version Downloads Last Updated
2.1.1 130 5/25/2026
2.1.0 91 5/25/2026
2.0.2 93 5/24/2026
2.0.1 87 5/24/2026
2.0.0 107 5/24/2026
1.1.0 85 5/18/2026
1.0.2 92 5/18/2026
1.0.1 92 5/18/2026
1.0.0 89 5/18/2026
0.3.0-preview.3 54 5/18/2026
0.3.0-preview.2 49 5/16/2026
0.3.0-preview.1 51 5/16/2026
0.2.0-preview.1 65 5/16/2026

v2.1.1
- Fix: MappShark.Generator.dll is now correctly included in the analyzers/dotnet/cs package path. The package was previously built without first compiling the generator in Release configuration, resulting in a missing analyzer DLL.

v2.1.0
- New: [MapFrom] now supports dot-path notation for navigating nested properties without a profile. Example: [MapFrom("Author.UserName")] maps source.Author.UserName to the destination property. Arbitrary depth is supported (e.g. "A.B.C.D"). Works on regular properties and positional record parameters. The source generator emits a verbatim property-chain expression at compile time; the reflection fallback builds a chained getter at runtime. New diagnostic MSP017 is reported when any path segment cannot be resolved to a public property.

v2.0.2
- Fix: MappSharkProfile subclasses now correctly trigger generated mapper code. Previously GetProfileInvocation used methodSymbol.ContainingType which returns the *declaring* type (MappSharkProfile), not the *calling* type (e.g. PostMappingProfile). The base-type check therefore always failed silently — profiles never generated mapper pairs in any prior version. Fixed by walking the syntax tree to find the calling class.
- Fix: Source generator now skips type pairs where either type is private or protected, avoiding inaccessible-type compile errors when the generator is properly wired as an analyzer in a test/consumer project. Private types fall back to the reflection mapper as intended.
- Fix: Test project now correctly wires the source generator with OutputItemType="Analyzer" so profile-based ForMember mappings are exercised via generated code rather than the reflection fallback.

v2.0.1
- Fix: ForMember overrides in a MappSharkProfile are no longer silently discarded when the same type pair was already registered by a Mapper.Map/MapMany call. The source generator now merges ForMember overrides from a profile into an existing pair registration instead of ignoring them.

v2.0.0
- New: ForMember in MappSharkProfile — define custom property mappings with lambda expressions, including nested paths (src => src.User.UserName) and computed values (src => src.PostVotes!.Count(v => v.IsRelevant)). Chain .ForMember(...) on CreateMap<>() inside any profile.
- New: Mapper.UseProfile<TProfile>() — registers a profile's ForMember delegates for the reflection-based mapping path. Not required when the Roslyn source generator is active.
- Breaking: MappSharkProfile.CreateMap<TSource, TDestination>() now returns IMapConfiguration<TSource, TDestination> instead of void. Existing profiles that ignore the return value continue to compile without changes.

v1.1.0
- New: [MapConverter] now works with [MapTo] on source properties (and record parameters). Converters placed on the source side are applied during both generated and reflection-based mapping, keeping the destination type free of any MappShark attributes.

v1.0.2
- Fix: positional record parameters that have no matching source property ("orphan" parameters) are now correctly skipped during reflection-based mapping — their constructor slot receives the CLR default value (null / default(T)) instead of throwing InvalidOperationException at runtime. This restores the intended behaviour where only annotated parameters are mapped and unannotated ones are left as developer-supplied defaults.

v1.0.1
- Added package icon.

v1.0.0
- First stable release of MappShark.
- Roslyn source generator emits zero-reflection mappers at compile time.
- [MapIndex(n)]: index-based property mapping across differently named types.
- [MapFrom("SourceProp")] / [MapTo("DestProp")]: attribute-driven name remapping.
- Name-based fallback: properties with the same name are mapped automatically without annotations.
- Mapper.BothWays: generates forward and reverse mappings in one call.
- Mapper.MapMany / Mapper.ProjectMany: collection and IQueryable projection support.
- MappSharkProfile: centralize all mapping registrations in a profile class.
- Init-only properties and positional records fully supported via generated initializer/constructor syntax.
- Mapping errors (missing indexes, attribute conflicts) surface as MSBuild diagnostics (MSP0xx).
- Native AOT and trimming friendly — no runtime reflection on the hot path.
- Targets netstandard2.0 and net8.0.

v0.3.0-preview.3
- Records and init-only properties: source generator now emits object-initializer syntax for destination types with init-only setters (record { get; init; }).
- [MapFrom] / [MapTo] work seamlessly with init-only destination properties.
- Positional records fully supported via generated constructor-call syntax (new T(Param: val)) — no reflection fallback.
- [MapIndex], [MapFrom], and [MapTo] can be placed directly on positional parameters; [property: ...] specifier also accepted.
- Removed where TDestination : new() constraints from Mapper.Map, Mapper.BothWays, and Mapper.MapMany.
- Projection (LINQ expression trees) is not supported for positional records.

v0.3.0-preview.2
- Fix: [MapFrom]/[MapTo] and name-based fallback now correctly resolve properties inherited from base classes.
- Fix: [MapIndex] on base class properties is now discovered by the source generator.

v0.3.0-preview.1
- [MapFrom("SourceProp")]: apply on a destination property to read from a differently-named source property.
- [MapTo("DestProp")]: apply on a source property to write to a differently-named destination property.
- Both attributes keep the domain model clean — no MappShark references needed in the domain.
- [MapFrom]/[MapTo] take priority over name-based fallback; conflicts with [MapIndex] are build errors (MSP015/MSP016).
- BothWays: each direction resolves [MapFrom]/[MapTo] independently — no ambiguity.

v0.2.0-preview.1
- Name-based fallback, BothWays, MapMany, IQueryable projections, Profiles, MSP013.