The AutoMapper.Internal namespace and the .Internal() escape hatches (InternalApi, IGlobalConfiguration, IGlobalConfigurationExpression, IProfileExpressionInternal, and the ~41 [EditorBrowsable(Never)] members alongside them) were introduced to shrink the discoverable public surface, on the theory that fewer visible knobs means fewer edge-case issues to triage.
Worth re-examining whether that has actually paid off, because the cost side is now visible.
Why now
#4655 turned up a concrete example. The only way to express blanket "ignore null source values" PATCH semantics today is:
cfg.Internal().ForAllPropertyMaps(pm => pm.SourceMember != null, (pm, opt) =>
{
var member = pm.SourceMember;
opt.PreCondition((object src) => ((PropertyInfo)member).GetValue(src) != null);
});
That works — I verified it. But it lives on IProfileExpressionInternal, is hidden from IntelliSense, and appears nowhere in the docs. Five issues over eight years (#2918, #2999, #3926, #4530, #4627) asked for exactly this behavior and none of them surfaced it. So in that instance hiding the API did not prevent the issues; it just meant nobody could find the answer, and the issues kept arriving anyway.
Questions to answer
- Did issue volume actually drop after the
Internal split, or did the same questions keep coming in a different shape?
- Which
Internal members are genuinely implementation detail (subject to change without notice) versus supported-but-advanced? Those are two different things currently sharing one namespace and one [EditorBrowsable(Never)] marker.
- For the supported-but-advanced set: promote to the public surface, document, and accept the triage cost — or keep hidden but document, so the answer is at least findable from a search?
- Does
[EditorBrowsable(Never)] carry its weight, given it hides from IntelliSense but not from source, samples, or Stack Overflow answers?
- What is the ApiCompat/versioning story if members move between the two? Anything promoted out of
Internal becomes a compatibility commitment.
ForAllPropertyMaps is the first concrete candidate for promotion, but the point of this issue is the policy, not that one member.
The
AutoMapper.Internalnamespace and the.Internal()escape hatches (InternalApi,IGlobalConfiguration,IGlobalConfigurationExpression,IProfileExpressionInternal, and the ~41[EditorBrowsable(Never)]members alongside them) were introduced to shrink the discoverable public surface, on the theory that fewer visible knobs means fewer edge-case issues to triage.Worth re-examining whether that has actually paid off, because the cost side is now visible.
Why now
#4655 turned up a concrete example. The only way to express blanket "ignore null source values" PATCH semantics today is:
That works — I verified it. But it lives on
IProfileExpressionInternal, is hidden from IntelliSense, and appears nowhere in the docs. Five issues over eight years (#2918, #2999, #3926, #4530, #4627) asked for exactly this behavior and none of them surfaced it. So in that instance hiding the API did not prevent the issues; it just meant nobody could find the answer, and the issues kept arriving anyway.Questions to answer
Internalsplit, or did the same questions keep coming in a different shape?Internalmembers are genuinely implementation detail (subject to change without notice) versus supported-but-advanced? Those are two different things currently sharing one namespace and one[EditorBrowsable(Never)]marker.[EditorBrowsable(Never)]carry its weight, given it hides from IntelliSense but not from source, samples, or Stack Overflow answers?Internalbecomes a compatibility commitment.ForAllPropertyMapsis the first concrete candidate for promotion, but the point of this issue is the policy, not that one member.