An address key is a normalized comparison string built from an address’s parts. Pass the country (Region) you know when parsing; compare keys to group spelling and layout variants.
dotnet add package RecursiveAI.OneContactRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using RecursiveAI.OneContact; var first = PostalAddress.Parse( "10-123 Main St, Toronto ON M5V 2T6", Region.CA); var second = PostalAddress.Parse( "123 Main Street Apt 10, Toronto ON M5V 2T6", Region.CA); Console.WriteLine(first.Key == second.Key);
For a list, use GroupBy(address => address.Key) as shown in the full program.
TrueUnzip the example, then dotnet run.
Full program
These made-up US addresses include a ZIP+4 variant and a different apartment. The first address in each group supplies its display text.
using RecursiveAI.OneContact; string[] inputs = [ "123 North Main Street Apartment 4, Springfield IL 62704-1234", "123 N Main St Apt 4, Springfield IL 62704", "123 N Main St Apt 5, Springfield IL 62704" ]; var addresses = inputs.Select(text => PostalAddress.Parse(text, Region.US)); foreach (var group in addresses.GroupBy(address => address.Key)) { Console.WriteLine($"{group.Count()} candidate(s): {group.Key}"); Console.WriteLine(group.First().Canonical); }
2 candidate(s): us|123 north main street unit 4|springfield|il|62704 123 N MAIN ST APT 4, SPRINGFIELD IL 62704-1234 1 candidate(s): us|123 north main street unit 5|springfield|il|62704 123 N MAIN ST APT 5, SPRINGFIELD IL 62704
If parsing fails, PostalAddress.Parse throws ContactParseException; the sample stops rather than dropping that address.
Variations
Pin the comparison rule
using RecursiveAI.OneContact; var address = PostalAddress.Parse( "123 Main St, Springfield IL 62704", Region.US); Console.WriteLine(address.GetKey(KeyVersion.V1));
us|123 main street|springfield|il|62704Store the key version alongside a persisted key. A published version’s output never changes; future rule changes use a new version.
Related recipes
The short answer
Parse each address with the country context you actually know, then group by PostalAddress.Key. Keep the canonical address as display text, not the key. Equal keys are duplicate candidates for your application to review; they do not establish a person’s identity or prove that mail can be delivered.
The snippet at the top compares two Canadian unit layouts. The full program groups US street abbreviations and a ZIP+4 variant while keeping apartment 5 separate from apartment 4. Neither example performs fuzzy matching or removes a record automatically.
How do I remove duplicate postal addresses from a C# list?
Compare parsed delivery components rather than the original strings. A string comparison would see “North” and “N” as different text. The address key expands the directional and street type into common forms and normalizes the unit designator. LINQ’s GroupBy then collects the records that share those components, as the full program shows.
Do not lowercase and strip punctuation from an entire address as a substitute for parsing. A house-number range and a unit separator can affect what an address means. OneContact builds the key from fields, retaining delivery-line order and unit numbers. It folds case, Latin diacritics and word punctuation using the rules described in the address key documentation.
For an import, retain an input record ID and original text beside the parsed value. Review failed parses separately. Keeping the first canonical address is the sample’s display policy, not a library rule about which source is correct. A business application might instead keep the most recently confirmed address or ask the user to choose.
How do I remove duplicates by an address property in C#?
Use the address key as the grouping property, not the canonical string. The canonical form is for reading; the key is for comparison. Store both if the database needs both roles. A key contains the country, delivery lines, locality, administrative area and postal code, joined by separators.
The version 1 rules deliberately use only the five-digit part of a US ZIP+4. Recipient, organization and attention lines never enter the key. Consequently, two people at one delivery address can share a key, and an address with extra ZIP digits can match one without them. That is appropriate for a delivery-address candidate group, not for merging customer accounts.
Numbered unit designators such as “apt” and “suite” normalize to “unit”, but their numbers remain. Canadian 10-123 Main St and 123 Main Street Apt 10 match; a different unit does not. Floors and buildings retain their own dictionary words rather than all becoming numbered units.
When persisting comparison keys, explicitly use GetKey(KeyVersion.V1) and save that version with the value. The versioned output is stable; a new comparison policy should not silently reinterpret old stored keys. Preserve the underlying address so you can compute another version later without treating the old comparison key as recoverable address text.