JasonBock / InlineMapping

Using the Compiler API for object mapping
MIT License
65 stars 11 forks source link

Provide Customization Flags #12

Open JasonBock opened 4 years ago

JasonBock commented 4 years ago

One idea is to allow the developer to provide hooks to the mapping process:

[MapTo(typeof(Destination), createDestination: true, postCustomization: true)]

If createDestination is true, a Func<Destination> parameter is created to allow the developer to create it. This may be useful when the destination type has a constructor with parameters. Note that if this is true, the diagnostic that looks for public, no-argument constructors would have to be updated. If postCustomization is true, a Action<Source, Destination> parameter is created to allow the developer to handle complex mapping cases. Here's an example if both of these flags are set to true:

var destination = source.MapToDestination(
() => new Destination(3),
(s, d) =>
{
  d.MapThis = s.ToThis;
});

I'm not sure about postCustomization. A developer could simply do this if they want:

var destination = source.MapToDestination();
destination.MapThis = source.ToThis;

I'm basically putting this idea out to think about and come back to later. I guess if you needed to do this customization, having the callback as a parameter "forces" the requirement, rather than having to remember to do the post-processing. But it also feels like it's somewhat unnecessary.

The createDestination flag I think has more value. It's definitely possible that destination types will not always have no-argument constructors, so giving the developer the ability to create the destination has value. However, I'm not sure how this will work with init properties as well, so there be dragons here.

JasonBock commented 3 years ago

I think createDestination should probably be provideDestination. I've already changed InlineMapping to create extension methods for all of the accessible constructors on the destination type, so that's covered. provideDestination means you give a pre-existing value of the destination type. There might be a really odd corner case with:

public Destination(Destination value)

If I'm specifying provideDestination, do I generate extension methods for constructors as well? If the name of the extension method would be:

public static Destination MapToProvidedDestination(this Source self, Destination destination) ...

Then I wouldn't run into a collision with:

public static Destination MapToDestination(this Source self, Destination destination) ...

The former would be used to map self to an existing destination. The latter is passing destination into a created Destination object and mapping to that new Destination object.

JasonBock commented 3 years ago

If I have provideDestination, then I can't use object initialization, so the generated code has to be:

destination.PropertyA = source.PropertyA;
destination.PropertyB = source.PropertyB;
// ...
return destination;

This means I need to make sure that in this case, I only find accessible setters, not setters and setters-via-init properties.

For all generated mapping methods, if postCustomization is set, then I create an Action<Source, Destination> parameter. Note that this means the mapping method will return whatever the Destination value is from the Action....but if it's a struct, then....hmmmm....need to think that through as well.