Skip to content

internal: seal singleton-style types with no accessible constructor - #424

Merged
twcclegg merged 1 commit into
mainfrom
internal/seal-singleton-entry-points
Aug 26, 2026
Merged

internal: seal singleton-style types with no accessible constructor#424
twcclegg merged 1 commit into
mainfrom
internal/seal-singleton-entry-points

Conversation

@twcclegg

Copy link
Copy Markdown
Owner

Summary

Part of the sealing/visibility audit from #375 (comment: #375 (comment)) — this PR is the "safe now, not blocked on a major version" bucket.

  • PhoneNumberUtil, PhoneNumberOfflineGeocoder, PhoneNumberToCarrierMapper, PhoneNumberToTimeZonesMapper, ShortNumberInfosealed. All are reached only through a static GetInstance(); their constructors are internal/private, so no external assembly could ever have derived from them, and nothing in this repo does either.
  • MetadataFiltersealed. Its own doc comment already says it's "an internal API ... not part of the public API"; every member except the class declaration and the object overrides is already internal.

Verified locally that this is genuinely non-breaking: ran dotnet pack -p:VersionPrefix=9.0.38 against the 9.0.37 EnablePackageValidation baseline. sealed on a type with no accessible constructor produces zero ApiCompat errors — the CP0009 "sealed added" rule only fires when the baseline type had a public/protected constructor. (I also tried making MetadataFilter internal outright, matching its doc comment, but that trips CP0001 "type removed from the public API surface" regardless of constructor accessibility — so it stays public, just sealed. Posted that correction on #375 too.)

No behavior change.

Test plan

  • dotnet build csharp/PhoneNumbers.slnx — clean, 0 warnings
  • dotnet test csharp/PhoneNumbers.slnx -p:TargetFrameworks=net10.0 — 450/450 passing (414 PhoneNumbers.Test + 36 PhoneNumbers.Extensions.Test)
  • dotnet pack -c Release csharp/PhoneNumbers -p:VersionPrefix=9.0.38 — ApiCompat clean against the 9.0.37 baseline

PhoneNumberUtil, PhoneNumberOfflineGeocoder, PhoneNumberToCarrierMapper,
PhoneNumberToTimeZonesMapper, and ShortNumberInfo all have internal or
private constructors and are reached only through GetInstance() - no
external assembly could ever have subclassed them, and nothing in this
repo does either. MetadataFilter is also sealed: its own doc comment
already says it's "an internal API ... not part of the public API",
every member but the class declaration and the object overrides is
already internal.

Verified locally with `dotnet pack -p:VersionPrefix=9.0.38` against the
9.0.37 ApiCompat baseline that sealing these produces zero CP-rule
errors - the CP0009 "sealed added" rule only fires for a type with an
accessible (public/protected) constructor. (Tried making MetadataFilter
`internal` too, matching its doc comment, but that trips CP0001 "type
removed" regardless of constructor accessibility, so it stays public
and just gains `sealed` - see the correction posted on #375.)

Follow-up from the #375 audit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.40%. Comparing base (eb5f0e8) to head (2592935).
⚠️ Report is 9 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #424      +/-   ##
==========================================
- Coverage   87.46%   87.40%   -0.06%     
==========================================
  Files          41       41              
  Lines        3837     3835       -2     
  Branches      984      985       +1     
==========================================
- Hits         3356     3352       -4     
- Misses        280      281       +1     
- Partials      201      202       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown

📊 Benchmark Results

Commit: 2592935 · Full run · Linux ubuntu-24.04-arm

Both sides were measured on the same runner in the same job, so the numbers are
comparable. Treat sub-percent differences as noise.

PR branch

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
InputDigitPerKeystroke 1000 4.318 ms 0.0118 ms 0.0110 ms 54.6875 3.88 MB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]     : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  Job-AMQORM : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Runtime=.NET 10.0  InvocationCount=1  IterationCount=20  
LaunchCount=1  RunStrategy=ColdStart  UnrollFactor=1  
WarmupCount=1  

Method Mean Error StdDev Allocated
CreateInstance 409.5 μs 102.8 μs 118.4 μs 119.48 KB
CreateInstanceAndLoadAllRegions 7,088.3 μs 379.9 μs 437.5 μs 1620.34 KB
FirstRegionLookup 459.6 μs 147.0 μs 169.3 μs 124.54 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
ExtractPossibleNumber_CleanInput 1000 20.91 μs 0.024 μs 0.023 μs - -
ExtractPossibleNumber_WithLeadingJunk 1000 38.12 μs 0.061 μs 0.057 μs 0.6714 48360 B

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
FindNumbers_Valid 100 141.1 μs 0.24 μs 0.21 μs 0.9766 70.71 KB
FindNumbers_StrictGrouping 100 314.4 μs 0.56 μs 0.50 μs 1.4648 124.84 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
GetDescriptionForNumber 1000 1,408.54 μs 2.859 μs 2.534 μs 1.9531 196.59 KB
GetDisplayCountry 1000 18.75 μs 0.028 μs 0.026 μs 0.0916 7.56 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
ParseValidateAndFormatPhoneNumbers 1000 2,526.9 μs 4.52 μs 4.01 μs 7.8125 577.71 KB
ParseOnly 1000 435.9 μs 0.51 μs 0.45 μs 4.8828 348.13 KB
ParseNationalFormat 1000 768.5 μs 2.33 μs 2.17 μs 5.8594 431.36 KB
ParseWithExtension 1000 1,103.8 μs 0.96 μs 0.85 μs 13.6719 957.38 KB
ValidateOnly 1000 730.7 μs 1.30 μs 1.16 μs - 41.11 KB
FormatOnly 1000 1,083.8 μs 6.14 μs 5.44 μs 1.9531 187.92 KB
PR base

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
InputDigitPerKeystroke 1000 4.303 ms 0.0100 ms 0.0089 ms 54.6875 3.88 MB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]     : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  Job-AMQORM : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Runtime=.NET 10.0  InvocationCount=1  IterationCount=20  
LaunchCount=1  RunStrategy=ColdStart  UnrollFactor=1  
WarmupCount=1  

Method Mean Error StdDev Allocated
CreateInstance 402.8 μs 95.89 μs 110.4 μs 119.48 KB
CreateInstanceAndLoadAllRegions 7,007.8 μs 378.91 μs 436.4 μs 1620.34 KB
FirstRegionLookup 449.8 μs 132.27 μs 152.3 μs 124.54 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
ExtractPossibleNumber_CleanInput 1000 21.65 μs 0.025 μs 0.023 μs - -
ExtractPossibleNumber_WithLeadingJunk 1000 38.38 μs 0.042 μs 0.039 μs 0.6714 48360 B

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
FindNumbers_Valid 100 141.3 μs 0.21 μs 0.17 μs 0.9766 70.71 KB
FindNumbers_StrictGrouping 100 314.2 μs 1.20 μs 1.06 μs 1.4648 124.84 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
GetDescriptionForNumber 1000 1,372.35 μs 10.809 μs 9.582 μs 1.9531 196.59 KB
GetDisplayCountry 1000 18.45 μs 0.021 μs 0.019 μs 0.0916 7.56 KB

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 4 physical cores
.NET SDK 10.0.400
  [Host]    : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a
  .NET 10.0 : .NET 10.0.11 (10.0.11, 10.0.1126.37416), Arm64 RyuJIT armv8.0-a

Job=.NET 10.0  Runtime=.NET 10.0  

Method PhoneNumberCount Mean Error StdDev Gen0 Allocated
ParseValidateAndFormatPhoneNumbers 1000 2,526.2 μs 6.07 μs 5.07 μs 7.8125 577.71 KB
ParseOnly 1000 428.2 μs 0.57 μs 0.51 μs 4.8828 348.13 KB
ParseNationalFormat 1000 776.9 μs 1.53 μs 1.43 μs 5.8594 431.36 KB
ParseWithExtension 1000 1,134.4 μs 1.89 μs 1.77 μs 13.6719 957.38 KB
ValidateOnly 1000 735.4 μs 1.60 μs 1.42 μs - 41.11 KB
FormatOnly 1000 1,064.8 μs 1.59 μs 1.41 μs 1.9531 187.92 KB

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant