Repository navigation
Generated code
The package contains a source generator. For each Mapper.Map<A, B>(x) call where A and B are
concrete types, it emits a method like this and redirects the call to it with a C# interceptor:
internal static UserDto Map0(UserEntity inputObject)
{
if (inputObject is null)
{
throw new ArgumentNullException(nameof(inputObject));
}
return new UserDto
{
Id = inputObject.Id,
Name = inputObject.Name,
Tags = inputObject.Tags
};
}That is the code you would write by hand, and it runs at the same speed. Nothing to configure: the package turns interceptors on for you.
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>Build, then open obj/<Configuration>/<tfm>/generated/DotNetMapper.Generator/DotNetMapper.Generator.MapperGenerator/DotNetMapper.Interceptors.g.cs.
Each [InterceptsLocation] attribute is one call site.
These calls run the runtime fallback instead. It gives the same result, and costs a few nanoseconds more per call after the first.
| Call | Why |
|---|---|
Mapper.Map<TIn, TOut>(x) inside your own generic method |
The types are only known at runtime |
Private, protected or file-local types |
Generated code cannot name them |
| Anonymous types (through a generic helper) | Generated code cannot name them |
Func<A, B> f = Mapper.Map<A, B>; |
A method group is not a call |
Inside Expression<...>, including query clauses over IQueryable
|
Deliberate, see below |
Package referenced with ExcludeAssets="analyzers"
|
The generator is off |
The first call for each type pair compiles an expression tree once and caches it. Later calls cost one delegate call.
A call inside an expression tree is left as Mapper.Map, so a query provider sees the real method
instead of a generated one it has never heard of:
var dtos = db.Users.Select(u => Mapper.Map<User, UserDto>(u)).ToList();EF Core runs a method call in the final Select on the client, after it has loaded the whole
entity. If you need SQL to fetch only the columns of the DTO, write that projection by hand.
Intercepted calls are plain code and need nothing. Under NativeAOT the runtime fallback interprets its expression tree instead of compiling it: still correct, slower.
If you wrap Map in your own generic method with trim analysis on (IsAotCompatible,
PublishTrimmed or PublishAot), you get:
warning IL2091: 'TInput' generic argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties'
in 'DotNetMapper.Mapper.Map<TInput, TOutput>(TInput)'. The generic parameter 'TIn' of
'Convert<TIn, TOut>(TIn)' does not have matching annotations.
Copy the attributes from Map onto your type parameters:
using System.Diagnostics.CodeAnalysis;
static TOut Convert<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TIn,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOut>(TIn input)
where TOut : new()
=> Mapper.Map<TIn, TOut>(input);<PackageReference Include="DotNetMapper" Version="2.0.0" ExcludeAssets="analyzers" />Every call then uses the runtime fallback. Useful to rule the generator out when you suspect it.
DotNetMapper
Guides
Links