Minicon.CodeMapper
0.2.2
dotnet add package Minicon.CodeMapper --version 0.2.2
NuGet\Install-Package Minicon.CodeMapper -Version 0.2.2
<PackageReference Include="Minicon.CodeMapper" Version="0.2.2" />
<PackageVersion Include="Minicon.CodeMapper" Version="0.2.2" />
<PackageReference Include="Minicon.CodeMapper" />
paket add Minicon.CodeMapper --version 0.2.2
#r "nuget: Minicon.CodeMapper, 0.2.2"
#:package Minicon.CodeMapper@0.2.2
#addin nuget:?package=Minicon.CodeMapper&version=0.2.2
#tool nuget:?package=Minicon.CodeMapper&version=0.2.2
Minicon.CodeMapper
Schlanker Objekt-zu-Objekt-Mapper für .NET auf Basis eines Source Generators – als
Ablösung für AutoMapper. Die vertraute Profile / CreateMap-DSL bleibt erhalten, die
Mappings werden jedoch zur Compile-Zeit als statischer, reflection-freier Code erzeugt.
- ⚡ Schnell – keine Reflection im heißen Pfad, nur direkte Zuweisungen und Delegate-Lookups
- 🧩 Vertraute API –
Profile,CreateMap<,>(),ForMember,MapFrom,Ignore,ReverseMap - 🔍 Compile-Zeit-Diagnostics – nicht zuordenbare Ziel-Member werden direkt im Build gemeldet
- 🪶 Trimming-/AOT-freundlich – der generierte Mapping-Code ist vollständig statisch
- 🆓 MIT-Lizenz – keine kommerzielle Lizenzpflicht
Status: v0.1 – die Kern-Features stehen und sind getestet. Siehe Roadmap.
Installation
Projektreferenz (während der Entwicklung):
<ProjectReference Include="path/to/src/Minicon.CodeMapper/Minicon.CodeMapper.csproj" />
Der zugehörige Source Generator ist im Paket enthalten und wird automatisch aktiv.
Schnellstart
1. Profile definieren – exakt wie gewohnt:
using Minicon.CodeMapper;
public class PersonProfile : Profile
{
public PersonProfile()
{
CreateMap<Address, AddressDto>().ReverseMap();
CreateMap<Order, OrderDto>();
CreateMap<Person, PersonDto>()
.ForMember(d => d.FullName, o => o.MapFrom(s => s.FirstName + " " + s.LastName))
.ForMember(d => d.Secret, o => o.Ignore());
}
}
2. Registrieren (Microsoft.Extensions.DependencyInjection):
services.AddCodeMapper(typeof(PersonProfile).Assembly);
3. Mappen:
public class PersonService(IMapper mapper)
{
public PersonDto ToDto(Person p) => mapper.Map<PersonDto>(p);
public List<PersonDto> ToDtos(IEnumerable<Person> people) => mapper.Map<List<PersonDto>>(people);
}
Ohne DI geht es auch statisch über Mapper.Instance:
var dto = Mapper.Instance.Map<PersonDto>(person);
Unterstützte Features (v0.1)
| Feature | Status | Hinweis |
|---|---|---|
CreateMap<TSource, TDestination>() |
✅ | Property-/Feld-Zuordnung nach Name |
ForMember(..., o => o.MapFrom(s => expr)) |
✅ | beliebiger Quell-Ausdruck |
ForMember(..., o => o.Ignore()) |
✅ | |
ReverseMap() |
✅ | erzeugt Auto-Mapping in Gegenrichtung (nicht in Kombination mit ConvertUsing) |
| Verschachtelte Objekte | ✅ | nutzt registriertes Sub-Mapping |
Collections (List<T>, T[], IEnumerable<T>, …) |
✅ | Element-Mapping |
Nullable<T> → T |
✅ | via GetValueOrDefault() |
| Implizite Konvertierungen | ✅ | z. B. int → long |
| Enum-Konvertierungen | ✅ | Enum ↔ Zahl, Enum ↔ Enum, string ↔ Enum (inkl. Nullable<TEnum>) |
ConvertUsing(...) |
✅ | vollständig benutzerdefinierte Konvertierung |
ConstructUsing(...) |
✅ | benutzerdefinierte Erzeugung, übrige Member werden weiter gemappt |
| Ziele ohne parameterlosen Konstruktor | ✅ | passender Konstruktor wird automatisch gewählt |
record / init-only Properties |
✅ | primärer Konstruktor + Objekt-Initializer |
BeforeMap / AfterMap |
🚧 | DSL vorhanden, noch ohne Codegen |
ValueResolver / Condition / NullSubstitute |
🚧 | geplant |
Konstruktoren
Hat der Zieltyp einen zugänglichen parameterlosen Konstruktor, wird wie gewohnt
new Dest { ... } erzeugt. Andernfalls wählt der Generator den Konstruktor mit den
meisten Parametern, die sich vollständig aus der Quelle befüllen lassen – Parameternamen
werden dabei case-insensitiv gegen Quell-Member und ForMember-Konfigurationen aufgelöst,
optionale Parameter dürfen offenbleiben:
public class Process
{
public Process(string name, int priority = 5) { Name = name; Priority = priority; }
public string Name { get; }
public int Priority { get; }
public List<ProcessParameter> Parameters { get; set; } = new();
}
// generiert:
var dest = new Process(name: source.Name, priority: source.Priority)
{
Parameters = mapper.Map<List<ProcessParameter>>(source.Parameters),
};
Bereits per Konstruktor befüllte Member werden im Objekt-Initializer nicht erneut gesetzt.
Passt kein Konstruktor – etwa weil die Parameter aus verschachtelten Quellen stammen –,
gibt ConstructUsing die Erzeugung vor. Anders als ConvertUsing ersetzt es nur die
Konstruktion; alle übrigen Member werden danach weiterhin automatisch gemappt:
CreateMap<ProcessEntity, string>()
.ConstructUsing(e => e.Name);
CreateMap<ProcessParameterEntity, ProcessParameter>()
.ConstructUsing(e => new ProcessParameter(
e.Process.Name,
e.Name,
e.Value,
Enum.Parse<ParameterSourceType>(e.ParameterSource.Name, true)));
init-only Properties lassen sich nach ConstructUsing nicht mehr setzen und gehören
deshalb in den Konstruktor-Ausdruck.
Enums
Enum-Werte werden ohne zusätzliche Konfiguration konvertiert – sowohl für Member als auch für Konstruktor-Parameter:
| Quelle | Ziel | Erzeugter Code |
|---|---|---|
int (o. a. Zahlentyp) |
TEnum |
(TEnum)source.X |
TEnum |
int |
(int)source.X |
TEnumA |
TEnumB |
(TEnumB)source.X |
string |
TEnum |
Enum.Parse<TEnum>(source.X, true) (case-insensitiv) |
TEnum |
string |
source.X.ToString() |
int? / TEnum? |
TEnum? |
gehobener Cast – null bleibt null |
string |
TEnum? |
null bei leerem/whitespace-Wert |
Nullable-Quellen auf nicht-nullable Ziele laufen wie bisher über GetValueOrDefault()
(int? → TEnum ergibt bei null also den Enum-Wert 0). Benutzerdefinierte
explicit operator-Konvertierungen werden ebenfalls genutzt.
Diagnosen
| ID | Severity | Bedeutung |
|---|---|---|
MINI001 |
Info | Ziel-Member konnte keiner Quelle zugeordnet werden und bleibt auf dem Standardwert. |
MINI002 |
Error | Kein zugänglicher Konstruktor des Zieltyps ist aus der Quelle befüllbar – fehlende Werte per ForMember(...MapFrom(...)) ergänzen oder die Erzeugung per ConstructUsing(...) vorgeben. |
MINI003 |
Error | Zieltyp ist kein konstruierbarer Objekttyp (string, Enum, Primitive, Interface, abstrakte Klasse) – hierfür ConstructUsing(...) oder ConvertUsing(...) verwenden. |
Migration von AutoMapper
In den meisten Fällen genügt das Ersetzen des Namespaces und der DI-Methode:
| AutoMapper | Minicon.CodeMapper |
|---|---|
using AutoMapper; |
using Minicon.CodeMapper; |
class X : Profile |
class X : Profile (unverändert) |
CreateMap<A, B>()... |
unverändert |
IMapper / mapper.Map<T>(x) |
unverändert |
services.AddAutoMapper(asm) |
services.AddCodeMapper(asm) |
Wie es funktioniert
- Der Source Generator findet alle
Profile-Subklassen und liest derenCreateMap/ForMember-Deklarationen aus dem Syntaxbaum. - Pro Typ-Paar wird eine statische Methode erzeugt (
new TDest { ... }mit direkten Zuweisungen). - Alle Methoden werden per
[ModuleInitializer]in derMapperRegistryregistriert – ganz ohne Assembly-Scan zur Laufzeit. IMapper.Map<T>()ist nur noch ein Dictionary-Lookup auf den passenden Delegate.
Auszug aus generiertem Code:
internal static PersonDto Map_3(Person source, IMapper mapper)
{
if (source is null) return default!;
return new PersonDto
{
Id = source.Id,
FullName = ((Func<Person, string>)(s => s.FirstName + " " + s.LastName))(source),
LuckyNumber = source.LuckyNumber.GetValueOrDefault(),
HomeAddress = mapper.Map<AddressDto>(source.HomeAddress),
Orders = mapper.Map<List<OrderDto>>(source.Orders),
// Secret wird ignoriert
};
}
Trimming & AOT
Der generierte Mapping-Code ist vollständig statisch typisiert und reflection-frei.
Einzige Ausnahme ist ein Komfort-Fallback in Mapper für Top-Level-Collection-Aufrufe
wie Map<List<T>>(...), der minimale Reflection nutzt (sauber annotiert). Für NativeAOT
empfiehlt es sich, registrierte Element-Mappings direkt zu verwenden; die automatische
Generierung dedizierter Collection-Maps ist auf der Roadmap.
Roadmap
- Inline-Codegen für Collections (vollständige AOT-Reinheit ohne Reflection-Fallback)
-
BeforeMap/AfterMapim Codegen -
Condition,NullSubstitute,ValueResolver - Flattening (
Order.Customer.Name→OrderDto.CustomerName) - NuGet-Veröffentlichung
- Drop-in-Kompatibilitätspaket (Namespace-Alias
AutoMapper)
Build & Test
dotnet build
dotnet test
Lizenz
MIT © Michael Nikolaus
| 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
-
net8.0
-
net9.0
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.