question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

.NET 8 zero overhead private member mapping

See original GitHub issue

Previously, Mapperly hasn’t had the option to access or set private members. Although private/hidden members have always been visible to source generators, there hasn’t been a suitable way of accessing them. While workarounds such as Reflection.Emit, Linq.Expressions and reflection are available, upon closer inspection, they are insufficient for our needs due to poor performance and incompatibilities in AOT usage. For these reasons, Mapperly has never added private member mapping.

With the release of .NET 8, the UnsafeAccessorAttribute will be added. This supports code that accesses internal methods, constructors, fields, and properties with zero overhead while being AOT compatible. By applying it to an extern static method and configuring it, the runtime will attempt to find the corresponding field or method, to which the call will be forwarded.

Using the new attribute, Mapperly could add support for private member mapping, init-only/private setter mapping, and support private constructors.

Private member mapping

Enabling

Private member mapping could be enabled by default, although this would likely lead to nasty unexpected behaviour while breaking backwards compatibility. Instead I suggest that a property EnablePrivateMapping be added to [Mapper] and a method attribute [EnablePrivateMapping] be added. Alternatively private member mapping could always be enabled but only for explicit MapProperty mappings.

Generated code

The private access methods can be added as additional methods to the mapping class. These could be implemented like a MethodMapping and used like so: SetId(target, GetId(source)). Alternatively, to make the code easier to read, the private accessors could be added to a file scoped static accessor class. This class way the methods could be implemented as extensions methods, with the resulting code reading left to right. target.SetId(source.GetId()).

target.Tires.SetSpareWheel(source.Tires.GetSpareWheel()) vs SetSpareWheel(target.Tires, GetSpareWheel(source.Tires))

Example

Mapper

public class Car
{
    private Tire _spareWheel { get; set; }
    // ...
}

public class CarDto
{
    private TireDto _spareWheel { get; set; }
    // ...
}

[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
public static partial class CarMapper
{
    [MapProperty("_spareWheel", "_spareWheel")]
    [MapProperty(nameof(Car.Manufacturer), nameof(CarDto.Producer))] // Map property with a different name in the target type
    public static partial CarDto MapCarToDto(Car car);
}

Generated Code

static file class 
{
    [UnsafeAccessor(UnsafeAccessorKind.Method, Name="set__spareWheel")]
    public static extern Tire GetSpareWheel(this Car source)

    [UnsafeAccessor(UnsafeAccessorKind.Method, Name="set__spareWheel")]
    public static extern void SetSpareWheel(this CarDto target, TireDto tireDto)
}

public static partial class CarMapper
{
    public static partial CarDto MapCarToDto(Car car)
    {
        target.SetSpareWheel(MapToTireDto(car.GetSpareWheel()));
        // ...
    }
}

Related #599, #531

Issue Analytics

  • State:open
  • Created 2 months ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
latonzcommented, Aug 2, 2023

@TimothyMakkison the idea of the Accessible flag is to control whether UnsafeAccessorAttribute s are used or only members which are accessible by the mapper are considered.

I don’t think InternalsVisibleTo affects a lot of people and there is an easy workaround with ignore attributes. IMO the enum would help for sure… We could introduce it and just report a diagnostic if the Accessible flag is not set until Mapperly supports the new Roslyn version and UnsafeAccessorAttribute.

We’ll release #597 as breaking change as it is actually a breaking change and we want to conform to the semantic release specification. I prepared #611 and #612 for this. The upgrade procedere for most of the users should be just as simple as upgrading to a new feature release.

1reaction
TimothyMakkisoncommented, Jul 31, 2023

Since https://github.com/riok/mapperly/pull/597 Mapperly maps all fields and properties the mapper has access to. This can include private members.

Yeah that makes this much easier 😅

For the accessors, to keep it simple, I’d reuse the same class and just append them at the end of the class.

I might try the separate static UnsafeAccessor class first. I think that extension methods will be more idiomatic and easier to read.

A diagnostic should emit if UnsafeAccessorAttribute is not supported but the Accessible flag is not set (eg. .NET < 8.0).

👍

public MemberVisibility IncludedMembers { get; set; } = MemberVisibility.AllAccessible;

Do you think that private/internal mapping should be enabled by default? Wouldn’t this be a massive breaking change?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Zero-overhead member access with suppressed visibility ...
We have work planned on new faster reflection APIs in .NET 8. The challenge with this option is that reflection APIs are always...
Read more >
mapperly
NET source generator for generating object mappings. Inspired by MapStruct. Because Mapperly creates the mapping code at build time, there is minimal overhead...
Read more >
C# Where does the memory overhead come from
Let's go with an overhead of 8 bytes. ... One complicated option could be to use memory mapped files, that would let you...
Read more >
Performance Improvements in .NET 7
NET 7 is fast. Really fast. This post deep-dives into hundreds of performance improvements that contributed to that reality.
Read more >
If everyone hates it, why is OOP still so widespread?
There is the overhead when reusing code that the creator of Erlang famously described as a case when you wanted a banana but...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found