This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
C# port of Google's libphonenumber. Code was rewritten from the Java source mostly unchanged — when in doubt about behavior, the Java upstream is the source of truth.
The library tracks upstream metadata releases (~every two weeks) via the create_new_release_on_new_metadata_update.yml GitHub Action; see commits like "feat: automatic upgrade to vX.Y.Z" for what those changes look like.
csharp/PhoneNumbers/— main library (NuGetlibphonenumber-csharp). Multi-targetsnetstandard2.0;net8.0;net9.0;net10.0.<TreatWarningsAsErrors>true</TreatWarningsAsErrors>is set, so warnings break the build.csharp/PhoneNumbers.Test/— xUnit tests, ported from the Java tests. Multi-targetsnetframework4.8;net8.0;net9.0;net10.0.csharp/PhoneNumbers.Extensions/— separate NuGet (libphonenumber-csharp.extensions) with C#-idiomatic helpers that don't exist in the Java library.csharp/PhoneNumbers.PerformanceTest/— BenchmarkDotNet harness.csharp/PhoneNumbers.MetadataBuilder/— build-time tool that converts XML metadata + geocoding/timezone text files into per-region binary files. Source-links a small set of files fromPhoneNumbers/so it doesn't depend on (and can't cycle with) the main library at build time.resources/— XML metadata (PhoneNumberMetadata.xml,ShortNumberMetadata.xml,PhoneNumberAlternateFormats.xml,PhoneNumberMetadataForTesting.xml), plusgeocoding/,carrier/,timezones/. These are copied verbatim from upstream — do not hand-edit. The library no longer reads them at runtime: the build pipeline emits binary equivalents underobj/metadata/,obj/geocoding/,obj/timezones/which are embedded into the published assembly.lib/update.sh+lib/DumpLocale.java— automation that pulls upstream resources and regeneratescsharp/PhoneNumbers/LocaleData.cs.
All dotnet commands run from the csharp/ directory unless noted.
Metadata is built from XML/text into per-region binary files at build time by
csharp/PhoneNumbers.MetadataBuilder/ (see the BuildBinaryMetadata,
BuildGeocodingBins, and BuildTimezoneBin MSBuild targets in PhoneNumbers.csproj).
You don't need to run anything by hand — dotnet build invokes the tool. The previous
geocoding.zip / testgeocoding.zip workflow is gone; the runtime reads binary files
directly via IMetadataLoader / BuildPrefixMapFromBin.
Build / test:
dotnet restore csharp
dotnet build csharp --no-restore
# Full test matrix:
dotnet test csharp/PhoneNumbers.sln
# Faster: net9.0 only (matches the Linux PR check):
dotnet test csharp/PhoneNumbers.sln -p:TargetFrameworks=net9.0Run a single test (xUnit filter syntax):
dotnet test csharp/PhoneNumbers.Test --filter "FullyQualifiedName~TestPhoneNumberUtil.TestParseNationalNumber"
dotnet test csharp/PhoneNumbers.Test --filter "FullyQualifiedName~TestPhoneNumberUtil" # whole classPack the NuGet packages (mirrors AppVeyor):
dotnet pack -c Release csharp/PhoneNumbers
dotnet pack -c Release csharp/PhoneNumbers.ExtensionsBenchmarks:
cd csharp/PhoneNumbers.PerformanceTest
dotnet run -c Release --framework net10.0 -- --filter "*"
dotnet run -c Release --framework net10.0 -- --filter "*PhoneNumberWorkflowBenchmark*"- Singleton + metadata loading.
PhoneNumberUtil.GetInstance()is the entry point. Region/country metadata is lazily loaded viaMetadataSource+IMetadataLoader(default impl:EmbeddedResourceMetadataLoader, which reads per-region binary files generated at build time byPhoneNumbers.MetadataBuilderand embedded underPhoneNumbers.metadata.<prefix>_<region-or-cc>). The XML parser (BuildMetadataFromXml.cs) is still used at build time and by the legacyPhoneNumberUtil(Stream)constructor for consumers loading custom XML, but is no longer on the default load path. - Generated files.
LocaleData.cs(~48k lines) andCountryCodeToRegionCodeMap.csare generated.LocaleData.csis regenerated byjavac DumpLocale.java && java DumpLocale > csharp/PhoneNumbers/LocaleData.cs(seelib/update.sh). Don't hand-edit either. - Partial-class TFM split.
PhoneNumberUtil.csis apartial classwith framework-specific halves:PhoneNumberUtil.net.cs(modern .NET) andPhoneNumberUtil.netstandard.cs(netstandard2.0 fallbacks). When adding APIs that use newer BCL features, put the polyfill on the netstandard side. - Subsystems and their entry types (each ports a Java counterpart of the same name):
PhoneNumberUtil— parse / format / validate.AsYouTypeFormatter— incremental formatting.PhoneNumberMatcher/PhoneNumberMatch— find numbers in free text.ShortNumberInfo— short codes / SMS shortcodes (separate metadata file).PhoneNumberOfflineGeocoder,PhoneNumberToTimeZonesMapper— geo/tz lookups; backed bygeocoding.zip/timezones/map_data.txt.AreaCodeMap+AreaCodeMapStorageStrategy/DefaultMapStorage/FlyweightMapStorage— prefix → string lookup used by geocoder/carrier/timezone mappers.
- Regex caching. Use
RegexCache/PhoneRegexrather than constructingRegexad hoc on hot paths — phone parsing is regex-heavy and the cache matters for throughput. - Nullable reference types are enabled only for
net8.0/net9.0targets, notnetstandard2.0(see csprojCondition). New code should still annotate.
- When fixing parsing/validation bugs, first check the upstream Java equivalent (
java/ingoogle/libphonenumber) — fixes that already exist upstream should be ported faithfully rather than reinvented. File and method names match closely (PhoneNumberUtil.java↔PhoneNumberUtil.cs,BuildMetadataFromXml.java↔BuildMetadataFromXml.cs, etc.). - Don't change
resources/*.xmlto fix metadata bugs. Those changes belong upstream; here they will be overwritten on the next automated metadata sync. - The XML-vs-protobuf and
CharSequencedivergences are documented incsharp/README.md("Known Issues") — be aware they exist if you see API shape differences from Java.
- PRs trigger
build_and_run_unit_tests_linux.yml(Ubuntu, .NET 9, net9.0 target only). AppVeyor (appveyor.yml) runs the full multi-TFM matrix on Windows and is the gate for NuGet publishes. - Releases are tag-driven on AppVeyor; metadata-bump releases are created automatically by
create_new_release_on_new_metadata_update.yml.