Text
Allocation-free character class checks and email normalization, offered alongside the equivalent regex patterns.
Features
CharacterChecks: vectorizedSearchValues-backed checks for digits, lowercase, uppercase and special characters overstringandReadOnlySpan<char>, plusClassifyandMissingfor reporting which character classes a value contains.CharacterClasses:[Flags]enum naming the ASCII character classes —Digit,Lower,Upper,Special.EmailAddress:TryNormalizeandIsValid, which canonicalize an address (lowercased domain, punycoded internationalized domain) before validating it withRegexCollection.EmailPattern.
These are alternatives to the patterns in Regular Expressions, not replacements: the regexes remain supported and unchanged in behavior.
Class Diagram
classDiagram
namespace Text {
class CharacterChecks {
<<static>>
+bool HasNumber(string value)
+bool HasLowerChar(string value)
+bool HasUpperChar(string value)
+bool HasSpecialChar(string value)
+CharacterClasses Classify(string value)
+CharacterClasses Missing(string value, CharacterClasses required)
}
class CharacterClasses {
<<enumeration>>
None
Digit
Lower
Upper
Special
}
class EmailAddress {
<<static>>
+bool TryNormalize(string value, out string normalized)
+bool IsValid(string value)
}
}
CharacterChecks ..> CharacterClasses : returnsEvery CharacterChecks method has a ReadOnlySpan<char> overload alongside the string one shown above.
Usage
Character class checks
using ArturRios.Util.Text;
bool hasDigit = "abc123".HasNumber(); // true
bool hasLower = "Hello".HasLowerChar(); // true
bool hasUpper = "Hello".HasUpperChar(); // true
bool hasSpecial = "Hello!".HasSpecialChar(); // true
// null is treated as containing nothing rather than throwing
string? missing = null;
bool none = missing.HasNumber(); // false
Reporting which requirements failed
Missing returns the required classes that are absent, which is what a validation message actually needs:
using ArturRios.Util.Text;
var required = CharacterClasses.Digit | CharacterClasses.Lower |
CharacterClasses.Upper | CharacterClasses.Special;
var missing = "ab1".Missing(required); // Upper | Special
if (missing != CharacterClasses.None)
{
Console.WriteLine($"Password still needs: {missing}"); // "Password still needs: Upper, Special"
}
// Classify reports what is present, in a single pass over the input
var present = "aB1".Classify(); // Digit | Lower | Upper
Email normalization
using ArturRios.Util.Text;
EmailAddress.TryNormalize("MA@Hostname.COM", out var normalized);
// true, normalized == "MA@hostname.com"
EmailAddress.TryNormalize("ma@münchen.de", out var punycoded);
// true, punycoded == "ma@xn--mnchen-3ya.de"
bool valid = EmailAddress.IsValid("ma@hostname.museum"); // true
Normalize before storing or comparing addresses, so two spellings of one mailbox compare equal.
Choosing between these and the regexes
| Need | Use |
|---|---|
| Check a character class, hot path | CharacterChecks — about 5x faster than the equivalent regex |
| Know which requirements a value failed | CharacterChecks.Missing |
| Validate input that may span multiple lines | CharacterChecks.Classify |
| Compare or store an email address | EmailAddress.TryNormalize |
| Accept an internationalized domain | EmailAddress.IsValid |
| A pattern to compose into a larger regex | RegexCollection constants |
| Strip matches from a string | RegexCollection + RegexExtensions.Remove |
Notes on behavior
- The checks are ASCII only, matching the regexes exactly. They deliberately avoid
char.IsDigitandchar.IsLower, which are Unicode-aware and would report the Arabic-Indic digit three as a digit and the German sharp s as a lowercase letter.Any(char.IsDigit)is also about twice as slow as the regex. CharacterChecks.Classifytolerates newlines.RegexCollection.HasNumberLowerAndUpperChar()does not, because.does not match a newline.EmailAddressdoes not useSystem.Net.Mail.MailAddress, which is a parser rather than a validator and accepts a great deal the pattern rejects, includinguser@-hostname.comanduser@hostname..TryNormalizedoes not trim surrounding whitespace: an address with stray spaces is rejected rather than silently repaired.