Practical .NET · Lesson 17

Small regular expressions with a precise contract

Validate a narrow identifier format without treating every text problem as a regex.

How it works

A regular expression describes a pattern in text. For a narrow identifier format, it can make a validation rule compact and testable. Anchors distinguish matching the entire input from merely finding a matching substring somewhere inside it. Quantifiers express exact or variable repetition, while character classes define which characters belong at each position.

Start with a written format rule. In this lesson a valid identifier consists of exactly two uppercase ASCII letters, a hyphen, and exactly three ASCII digits. That is a deliberately small contract, not a general rule for human names or international identifiers. An explicit pattern and prepared test cases make it easier to review what is accepted and what remains outside the format.

Walk through the example. The expression uses beginning and absolute-end anchors around the complete identifier. The first input matches. A lowercase prefix fails because the rule requires uppercase letters. A two-digit suffix fails because the final group requires exactly three digits, and an added prefix fails because the expression must cover the full input. Each call prints only the boolean decision.

Watch the boundary. The shorthand digit class can include more than ASCII digits under ordinary .NET regex behavior, so this specific machine format uses the explicit range zero through nine. Also avoid complicated backtracking patterns when a simple string check would express the rule more clearly. A finite timeout is a defensive execution boundary, but it does not replace understanding the pattern's behavior and accepted input sizes.

Practice independently. Design a code format containing three uppercase letters followed by four digits with no separator. Write valid and invalid boundary examples before writing the pattern. Include extra trailing characters, a leading space, and a non-ASCII digit. Then implement the same rule with straightforward length and character checks and compare which version is easier to understand and maintain for this small contract.

A working example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

var pattern = new Regex(@"\A[A-Z]{2}-[0-9]{3}\z", RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1));
foreach (string code in new[] { "AB-123", "ab-123", "AB-12", "XAB-123" })
    Console.WriteLine(pattern.IsMatch(code));

Expected output

True
False
False
False
Try this
Match the entire identifier, and use explicit ASCII ranges when the format requires them.