-
-
Notifications
You must be signed in to change notification settings - Fork 201
Description
Is your feature request related to a problem? Please describe.
When using [MapDerivedType] a switch expression is emitted with a throw expression for the default case.
This is necessary for correctness, but the compiler emits quite some IL for creating and throwing the exception.
public class Base { }
public class Derived : Base { }
[Mapper]
public static partial class A
{
[MapDerivedType<Derived, Derived>]
public static partial Base Map(Base source);
}Generates
// <auto-generated />
#nullable enable
public static partial class A
{
[global::System.CodeDom.Compiler.GeneratedCode("Riok.Mapperly", "4.2.1.0")]
public static partial global::Base Map(global::Base source)
{
return source switch
{
global::Derived x => x,
_ => throw new global::System.ArgumentException($"Cannot map {source.GetType()} to Base as there is no known derived type mapping", nameof(source)),
};
}
}Describe the solution you'd like
If we extract the throw expression to it's own method Throw the Map method becomes considerably smaller.
Smaller is not always better, if the extracted method is a hot path, as we then do an extra function call.
But for throw expressions we're already about to create and throw an Exception which by far over shadows the cost of the extra function call.
In .NET6 and upwards we can even use [StackTraceHidden] to exclude Throw from the stack trace to prettify it.
public static class B
{
public static Base Map(Base source)
{
return source switch
{
Derived x => x,
_ => Throw(source),
};
[StackTraceHidden]
static Base Throw(Base source) => throw new ArgumentException($"Cannot map {source.GetType()} to Base as there is no known derived type mapping", nameof(source));
}
}Additional context
Here's a benchmark of using the proposed throw helper.
| Method | Mean | Error | StdDev | Ratio | RatioSD | Code Size |
|------- |----------:|----------:|----------:|------:|--------:|----------:|
| MapA | 0.8429 ns | 0.0158 ns | 0.0148 ns | 1.00 | 0.02 | 739 B |
| MapB | 0.6197 ns | 0.0105 ns | 0.0093 ns | 0.74 | 0.02 | 155 B |using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Riok.Mapperly.Abstractions;
using System.Diagnostics;
BenchmarkRunner.Run<CloneBenchmarks>();
[DisassemblyDiagnoser(maxDepth: 2)]
public class CloneBenchmarks
{
public Base Value { get; set; }
[GlobalSetup]
public void GlobalSetup()
{
Value = new Derived();
}
[Benchmark(Baseline = true)]
public Base MapA() => A.Map(Value);
[Benchmark]
public Base MapB() => B.Map(Value);
}