Skip to content

Let parameterized provider registrations take effect after AddEssentials() [patch] - #136

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/132-parameterized-registration-replaces
Oct 18, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/132-parameterized-registration-replaces

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #132

Problem

Every parameterized Add…Provider(...) registered its instance with TryAddSingleton and TryAddEnumerable. Both of those deduplicate on the concrete type. AddEssentials() registers the defaults first, so a later call with arguments did nothing. For example, services.AddEssentials(); services.AddXorObfuscationProvider(key); kept obfuscating with the public default key. Likewise, AddNormalDistributionProvider(100, 15) still resolved N(0, 1).

Change

  • New Shared/ProviderRegistration.cs, linked like DistributionArguments.cs. AddOrReplaceSingleton<TService, TImpl> replaces an earlier registration of the same concrete type, both the concrete-type descriptor and its member of the interface set. It replaces each one in place, so the order AddEssentials() chose for single-interface resolution is kept. For example, Poisson stays the default IDiscreteDistribution after AddPoissonDistributionProvider(7). Keyed descriptors are skipped.
  • Xor, Caesar, BitRotate and all ten distributions register through it. That covers Bernoulli, Normal, Poisson, Geometric, Uniform, Exponential and LogNormal, plus Binomial, Triangular and Categorical, which have the same shape. Their "Calling this more than once is a no-op" remarks now describe the replacement.
  • AddEssentials() must not overwrite a provider the caller registered before it. The bundled registrations of those ten providers therefore go through AddDefaultOrKeep<T>. If the caller already registered the provider, its descriptor is moved to where the default would have gone, and no default is added. As a result, AddXorObfuscationProvider(key); AddEssentials(); also resolves the keyed Xor as the default IObfuscationProvider, and both call orders behave the same.
  • README's DI bullet and CLAUDE.md's Shared-file list are updated.

I chose replacing over throwing InvalidOperationException, the issue's second option. With throwing, AddEssentials() followed by any configured call would fail, so the natural composition would still not work. Parameterless providers keep their TryAdd registrations unchanged.

Tests

New ParameterizedRegistrationTests:

  • Xor_Key_Registered_After_AddEssentials_Takes_Effect and Xor_Key_Registered_Before_AddEssentials_Survives_It cover both call orders. The interface and the concrete type each resolve the keyed output, and Xor is listed once.
  • Normal_Parameters_Take_Effect_On_Either_Side_Of_AddEssentials covers both orders for a distribution.
  • Replacing_A_Registration_Keeps_Its_Place_And_Lists_It_Once covers Poisson, Caesar and BitRotate after AddEssentials(). The defaults per category don't change, and nothing is duplicated.
  • A_Second_Call_Replaces_A_Distribution_Outside_AddEssentials covers Triangular called twice.

With the fix reverted, all 5 tests fail. With the fix in place, the full Essentials.Tests suite passes (1043 tests), and dotnet build -c Release succeeds with no warnings.


Generated by Claude Code

…als() [patch]

TryAddSingleton/TryAddEnumerable deduplicate on the concrete type, so once
AddEssentials() had registered the defaults, AddXorObfuscationProvider(key)
or AddNormalDistributionProvider(100, 15) silently did nothing: data was
obfuscated with the public default key and distributions sampled the wrong
parameters.

The Xor, Caesar and BitRotate obfuscators and all ten distributions now
register through Shared/ProviderRegistration.cs, which replaces an earlier
registration of the same concrete type in place, so the order AddEssentials()
chose for single-interface resolution is kept. AddEssentials() in turn keeps
a registration the caller made before it, moving it to where the default
would have gone, instead of overwriting it.

Fixes #132

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011U7VEdh3f5sicnbD2XSo1a
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants