-
-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
.NET 8 zero overhead private member mapping #600
Comments
Relates #529 I like the idea 😊 Before this can be implemented we need to add support for Roslyn 4.7. Instead of public sealed class MapperAttribute
{
+ public MemberVisibility IncludedMembers { get; set; } = MemberVisibility.AllAccessible;
}
+[Flags]
+public enum MemberVisibility
+{
+ /// <summary>
+ /// Maps all accessible members.
+ /// </summary>
+ AllAccessible = All | Accessible,
+ /// <summary>
+ /// Maps all members.
+ /// </summary>
+ All = Public | Internal | Protected | Private,
+ /// <summary>
+ /// Maps only accessible members.
+ /// If not set, accessors for not directly accessible members using <see cref="UnsafeAccessorAttribute" /> are generated.
+ /// </summary>
+ Accessible = 1 << 0,
+ Public = 1 << 1,
+ Internal = 1 << 2,
+ Protected = 1 << 3,
+ Private = 1 << 4,
+} Accessible should replicate the current behaviour (mainly introduced by #597) which maps all members accessible by the mapper. For the accessors, to keep it simple, I'd reuse the same class and just append them at the end of the class. A diagnostic should emit if |
Yeah that makes this much easier 😅
I might try the separate static
👍
Do you think that private/internal mapping should be enabled by default? Wouldn't this be a massive breaking change? |
I would enable private/internal fields by default, but also set the #597 maps all members which are accessible by the mapper instead of only public and internal members. This is a breaking change for members in assemblies with the |
I think I misunderstood you earlier. When you say private do you mean map any private members or just private members that are in the scope of the mapper (aka nested mappers, I'd assume this would fall under [Mapper]
public statiic partial Mapper
{
// would _value be mapped by default?
public static partial B Map(A src);
}
public class A { private int _value { get; set; } }
public class B { private int _value { get; set; } }
Do other mapping libraries do this? I can't remember if reflection can use
I don't think its a massive problem. tbh I was suprised at how many people were using nested classes and wanted to map private properties. I have no idea what they are using it for |
@TimothyMakkison the idea of the Accessible flag is to control whether I don't think InternalsVisibleTo affects a lot of people and there is an easy workaround with ignore attributes. 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. |
Blocked by #682 |
@TimothyMakkison Have you already started working on this or do you have the desire to implement it? Otherwise I'd probably start working on it in the next few weeks 😊. |
Yeah I prototyped it a month ago. I have a working prototype but it needs revasing. Might need some help coming up with a nice abstraction to the changes I made to I can share the original if you want |
Oh yeah, should Mapperly let individual mapping methods set its visibility mapping? Or should it be configured in the |
A quick thought: Couldn't this be abstracted by introducing a new I think this might be helpful on method level, but IMO in a first version it is ok only on mapper level. |
FYI in #718 support for Roslyn 4.7 is prepared. |
Iirc I added
Maybe I'll try it later
I did try the extension version because IMO it was much easier to read. I do have concerns about the extension method overlapping with the types methods. |
🎉 This issue has been resolved in version 3.3.0-next.2 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
🎉 This issue has been resolved in version 3.3.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
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 explicitMapProperty
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())
vsSetSpareWheel(target.Tires, GetSpareWheel(source.Tires))
Example
Mapper
Generated Code
Related #599, #531
The text was updated successfully, but these errors were encountered: