|
| 1 | +# Organisation Patterns |
| 2 | + |
| 3 | +Source: https://learn.microsoft.com/dotnet/standard/base-types/regular-expression-source-generators |
| 4 | + |
| 5 | +## Where to declare generated regex |
| 6 | + |
| 7 | +The right placement depends on scope and reuse: |
| 8 | + |
| 9 | +### Dedicated static class (recommended for shared patterns) |
| 10 | + |
| 11 | +Centralise patterns that are used across multiple files or feature areas: |
| 12 | + |
| 13 | +```csharp |
| 14 | +// File: Patterns.cs (or RegexPatterns.cs) |
| 15 | +using System.Text.RegularExpressions; |
| 16 | + |
| 17 | +public static partial class Patterns |
| 18 | +{ |
| 19 | + [GeneratedRegex(@"^\d{4}-\d{2}-\d{2}$")] |
| 20 | + public static partial Regex IsoDate(); |
| 21 | + |
| 22 | + [GeneratedRegex(@"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$", |
| 23 | + RegexOptions.IgnoreCase, "en-US")] |
| 24 | + public static partial Regex Email(); |
| 25 | + |
| 26 | + [GeneratedRegex(@"^\+?[1-9]\d{6,14}$")] |
| 27 | + public static partial Regex PhoneE164(); |
| 28 | +} |
| 29 | +``` |
| 30 | + |
| 31 | +Usage anywhere in the assembly: |
| 32 | +```csharp |
| 33 | +if (!Patterns.Email().IsMatch(input)) { ... } |
| 34 | +``` |
| 35 | + |
| 36 | +### Declaring on the consuming class (private scope) |
| 37 | + |
| 38 | +When only one class needs the pattern, declare it there to keep the scope narrow: |
| 39 | + |
| 40 | +```csharp |
| 41 | +public partial class InvoiceParser |
| 42 | +{ |
| 43 | + [GeneratedRegex(@"INV-\d{6}", RegexOptions.IgnoreCase)] |
| 44 | + private static partial Regex InvoiceNumber(); |
| 45 | + |
| 46 | + public string? ExtractInvoiceNumber(string text) => |
| 47 | + InvoiceNumber().Match(text).Value; |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +The class must be `partial` — if it isn't already, make it so. |
| 52 | + |
| 53 | +### Feature-area file splitting |
| 54 | + |
| 55 | +For large modules with many patterns, split a single `partial` class across multiple files by feature area: |
| 56 | + |
| 57 | +``` |
| 58 | +Patterns/ |
| 59 | + Patterns.cs ← partial class declaration (no members) |
| 60 | + Patterns.Dates.cs ← date-related patterns |
| 61 | + Patterns.Email.cs ← email/address patterns |
| 62 | + Patterns.Finance.cs ← currency, IBAN, card numbers |
| 63 | +``` |
| 64 | + |
| 65 | +Each file: |
| 66 | +```csharp |
| 67 | +// Patterns.Dates.cs |
| 68 | +public static partial class Patterns |
| 69 | +{ |
| 70 | + [GeneratedRegex(@"^\d{4}-\d{2}-\d{2}$")] |
| 71 | + public static partial Regex IsoDate(); |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +### Co-location with a validator |
| 76 | + |
| 77 | +When a pattern is the heart of a validation class, keep it co-located: |
| 78 | + |
| 79 | +```csharp |
| 80 | +public static partial class PostalCodeValidator |
| 81 | +{ |
| 82 | + [GeneratedRegex(@"^\d{5}(-\d{4})?$")] |
| 83 | + private static partial Regex UsZip(); |
| 84 | + |
| 85 | + [GeneratedRegex(@"^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$", |
| 86 | + RegexOptions.IgnoreCase)] |
| 87 | + private static partial Regex UkPostcode(); |
| 88 | + |
| 89 | + public static bool Validate(string code, string country) => |
| 90 | + country switch |
| 91 | + { |
| 92 | + "US" => UsZip().IsMatch(code), |
| 93 | + "UK" => UkPostcode().IsMatch(code), |
| 94 | + _ => throw new ArgumentOutOfRangeException(nameof(country)) |
| 95 | + }; |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +## Naming conventions |
| 100 | + |
| 101 | +| Pattern | Recommended name | Rationale | |
| 102 | +|---------|-----------------|-----------| |
| 103 | +| Method form | Noun or noun phrase: `Email()`, `IsoDate()`, `PhoneNumber()` | The `()` implies a call; keep the name a descriptor of what it matches | |
| 104 | +| Property form | Same noun: `Email`, `IsoDate` | No parens, reads like a static constant | |
| 105 | + |
| 106 | +Avoid prefixes like `Get`, `Create`, or `Match` — the attribute already communicates the nature of the member. |
| 107 | + |
| 108 | +## Viewing the generated code |
| 109 | + |
| 110 | +In Visual Studio: right-click the method or property declaration → **Go To Definition**. |
| 111 | +Or expand **Dependencies → Analyzers → System.Text.RegularExpressions.Generator → RegexGenerator.g.cs** in Solution Explorer. |
| 112 | + |
| 113 | +The generated code is fully readable C# and can be stepped through in the debugger. |
0 commit comments