Extension methods
The CrispyWaffle.Extensions namespace holds a set of static helper classes, mostly extension methods, for common chores: converting strings to numbers and dates, cleaning and reshaping text, reading enum attributes, flattening exception chains, rounding integers, reflecting over types and reading XML.
Each class has no state and no dependencies to wire up. Add using CrispyWaffle.Extensions; and the methods appear on the types they extend.
| Package | CrispyWaffle |
| Namespace | CrispyWaffle.Extensions |
How it works
Most methods are forgiving. They return a neutral value (0, string.Empty, the original input, or false) for null, empty or whitespace input instead of throwing. The exceptions to this rule are listed in each table: for example ToDateTime throws on empty input, and Levenshtein does not check for null.
A few methods depend on legacy code pages that .NET (Core) does not load by default:
RemoveDiacriticsandLevenshteinInvariantCultureuse code page 1251.ToSafeUrluses ISO-8859-8.
On .NET Core and .NET 5 or later, register the code pages provider once at startup before you call these methods. The library does not do it for you.
using System.Text;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Parsing methods (ToInt32, ToInt64, ToDecimal, ToMonetary, TryToDateTime) use the current culture unless noted.
Getting started
using System;
using CrispyWaffle.Extensions;
var quantity = "1,250".ToInt32(); // 1250 under en-US; 0 if parsing fails
var due = "tomorrow".ToDateTime(); // DateTime.Today.AddDays(1)
var active = "s".ToBoolean(); // true: matches the default "S", case-insensitive
var title = "republica federativa do brasil".ToCamelCase(); // "Republica Federativa Do Brasil"
var slots = 15.RoundUp(); // 20
var place = 22.ToOrdinal(); // "22nd"
Reference
ConversionExtensions
public static class ConversionExtensions — conversions between strings, numbers, dates, booleans, bytes, JSON and XML.
| Member | Extends | Description |
|---|---|---|
ToBoolean(string validValueForTrue = "S") |
string |
Returns true only when the string equals validValueForTrue (case-insensitive). Any other value, including null, returns false. The default "S" is the Portuguese "Sim". |
ToString(string trueValue, string falseValue) |
bool |
Returns trueValue or falseValue. |
ToDateTime() |
string |
Parses with the same rules as TryToDateTime. Throws ArgumentNullException when the input is null or whitespace, and ArgumentOutOfRangeException when it cannot be parsed. |
TryToDateTime(out DateTime value) |
string |
Accepts the keywords now, today, yesterday and tomorrow (case-insensitive, trimmed), then DateTime.TryParse with the current culture, then the exact format dd/MM/yyyy for 10-character input. Returns false and DateTime.MinValue on failure. |
ToInt32() / ToInt64() / ToDecimal() |
string |
Parses with NumberStyles.Number and the current culture. Returns 0 for null, whitespace or unparseable input. |
ToBytes() |
IEnumerable<string> |
Converts each base-2 string (for example "01000001") to a byte. Throws FormatException on non-binary input. |
ToBinaryString() |
IEnumerable<byte> |
Converts each byte to an 8-character base-2 string. |
ToMonetary(CultureInfo cultureInfo = null) |
decimal |
Formats with {0:C} in cultureInfo or the current culture. Returns the literal "No value" for 0. |
ToUnixTimeStamp() |
DateTime |
Converts to UTC and returns the number of seconds since 1970-01-01 as an int. |
FromUnixTimeStamp() |
int |
Converts an epoch value to a local DateTime. The value is divided by 1000 (and rounded) before it is added as seconds, so it is treated as milliseconds. It is therefore not the inverse of ToUnixTimeStamp. |
ToPrettyString() |
string |
Re-formats a JSON object or array with indentation (Newtonsoft.Json). Returns string.Empty for null or whitespace and returns the input unchanged when IsValidJson is false. |
ToIdentString() |
XmlDocument |
Serializes the document with tab indentation and CRLF line breaks. Returns null for a null document. |
ToModule10() |
string |
Strips non-digits and computes a modulus-10 check digit with alternating weights 2 and 1 from the right (products of 10 or more have their digits summed). Returns 0–9. |
ToOrdinal() |
int, long |
Appends an English ordinal suffix (1st, 2nd, 3rd, 11th, 22nd). Negative numbers are returned without a suffix. |
DeepClone<T>(bool useNonPublic = true) |
T |
Builds a new T by calling its public constructor with the most parameters. Each argument comes from a property with the same name (case-insensitive) and exact type: public properties first, then non-public ones when useNonPublic is true. Property values are passed by reference, so nested objects are shared and the copy is shallow. Returns default when T has no public constructor. If a constructor parameter has no matching property, the argument list is too short and the constructor call throws. |
StringExtensions
public static class StringExtensions — cleaning, formatting, encoding and comparing strings. All methods are marked [Pure].
| Member | Extends | Description |
|---|---|---|
RemoveNonNumeric() |
string |
Removes every character except 0–9. |
RemoveNonAlphanumeric() |
string |
Removes every character that is not a word character (\w), ., @ or -. |
ReplaceNonAlphanumeric(string replaceWith) |
string |
Like RemoveNonAlphanumeric, but replaces each character with replaceWith. |
RemoveSpaces() |
string |
Removes all whitespace. |
RemoveExcessSpaces() |
string |
Replaces each run of two or more whitespace characters with a single space. The pattern is [\t\|\s]{2,}, so runs of \| characters are replaced as well. |
RemoveDiacritics() |
string |
Removes accents by round-tripping through code page 1251 and ASCII. Requires the code pages provider on .NET Core (see How it works). |
ReplaceFirst(string search, string replaceWith) |
string |
Replaces the first ordinal occurrence of search. Returns the input unchanged when search is not found. |
Abbreviate(int maxCharacters, bool addEllipsis = true) |
string |
Returns the input unchanged when it is shorter than maxCharacters. Otherwise truncates to maxCharacters - 4 characters and appends "..." (or to maxCharacters characters with no ellipsis). Returns string.Empty for null or whitespace. |
ToCamelCase() |
string |
Splits on spaces, lowercases each word and capitalizes its first letter, then joins with single spaces. Despite the name, the result is title case: "hello WORLD" becomes "Hello World". Returns string.Empty for null or whitespace. |
ToUcWords() |
string |
Lowercases the text, then uppercases each a–z letter at the start of the text or after whitespace, / or a digit. |
UcWords(string[] toUpper, string[] toLower) |
string[] |
Joins the words, each preceded by a space (so the result starts with a space). A word is fully uppercased when its uppercase form is in toUpper, lowercased when its lowercase form is in toLower, and passed to ToCamelCase otherwise. |
ToSafeUrl() |
string |
Builds a URL slug: transcodes through ISO-8859-8, removes - and ., collapses spaces, replaces remaining non-word characters with - and trims - from both ends. Case is preserved. Requires the code pages provider on .NET Core. |
ToBase64() |
string |
Encodes the UTF-8 bytes as Base64. Returns string.Empty for null or whitespace. |
FromBase64(string encoding = "ISO-8859-1") |
string |
Decodes Base64 and reads the bytes with encoding. Because the default is ISO-8859-1 and ToBase64 uses UTF-8, non-ASCII text does not round-trip unless you pass "UTF-8". |
FromISO2UTF8() |
string |
Encodes the string as ISO-8859-1, converts the bytes to UTF-8 and decodes them. Characters outside ISO-8859-1 become ?. |
ToValidFileName() |
string |
Replaces runs of characters from Path.GetInvalidFileNameChars(), and trailing dots, with _. Throws ArgumentNullException for null. |
Levenshtein(string inputToCompare) |
string |
Returns the Levenshtein edit distance (case-sensitive). Throws NullReferenceException if either string is null. |
LevenshteinInvariantCulture(string inputToCompare) |
string |
Removes diacritics and lowercases both strings, then calls Levenshtein. Requires the code pages provider on .NET Core. |
GetPathOrFileName() |
string |
Parses an absolute URL. Returns the file name when the path has an extension, otherwise the absolute path. Throws UriFormatException for relative or malformed URLs. |
GetFileName() |
Uri |
Returns Path.GetFileName of the local path, or string.Empty. |
GetFileExtension() |
Uri, string |
Returns the extension including the leading dot (for example .pdf), or string.Empty. |
ToCenter(char spacer, int lineSize) |
string |
Pads both sides with lineSize - input.Length / 2 copies of spacer (one more on the right for odd lengths). lineSize is effectively half the final width: the result is about 2 * lineSize characters long. |
IsValidJson() |
string |
Returns true when the trimmed string starts and ends with {} or [] and parses with JToken.Parse. Returns false for null, scalars and parse errors. |
SplitBy(int chunkLength) |
string |
Lazily yields chunks of chunkLength characters (the last may be shorter). Yields a single string.Empty for null or whitespace input. Throws ArgumentException when chunkLength < 1; because the method is an iterator, the exception surfaces on first enumeration. |
StripTags() |
string |
Removes everything from each < to the next >. Returns string.Empty for null or whitespace. |
EnumExtensions
public static class EnumExtensions — reads and resolves the HumanReadable and InternalValue attributes on enum fields. See Attributes.
| Member | Extends | Description |
|---|---|---|
GetHumanReadableValue(string flagsSeparator = " \| ") |
Enum |
Returns the [HumanReadable] text of the value, or null if the field has none. For a [Flags] enum with more than one bit set, joins the text of each set single-bit flag with flagsSeparator. In that case every set flag must have the attribute, or an IndexOutOfRangeException is thrown. Throws ArgumentNullException when a non-flags value does not match a declared field. |
GetInternalValue() |
Enum |
Returns the [InternalValue] text of the value, or null if the field has none. |
GetUniqueFlags<T>() |
Enum |
Lazily yields each declared single-bit value (as T, usually the enum type) that is set in the value. |
GetEnumByHumanReadableAttribute<T>(string humanReadableValue) |
— (static) | Returns the field of enum T whose [HumanReadable] text or field name equals humanReadableValue (case-insensitive). Throws InvalidOperationException when T is not an enum or more than one field matches, and ArgumentOutOfRangeException when nothing matches. |
GetEnumByInternalValueAttribute<T>(string internalValue) |
— (static) | Same as above, using [InternalValue]. Returns default(T) when internalValue is null. |
ExceptionExtensions
public static class ExceptionExtensions — flattens an exception chain and formats it. The logging infrastructure's default exception handler uses these methods; see Logging.
| Member | Extends | Description |
|---|---|---|
ToQueue(out List<Type> types) |
Exception |
Returns a queue holding the exception and all its inner exceptions, with the innermost exception first and the original exception last. types receives the types of the inner exceptions only (not the outer one), ordered from outer to inner. |
GetMessages(string category, ICollection<ILogProvider> additionalProviders) |
Queue<Exception> |
Dequeues every exception (the queue is empty afterwards) and builds one string with each message, the exception type name in brackets, and the stack trace. Entries after the first are prefixed with Exception rethrow at [n]: and a space, where n is the entry's position. For each exception, also calls Error(category, message) on every provider in additionalProviders, which must not be null. |
MathExtensions
public static class MathExtensions — rounds integers to a multiple.
| Member | Extends | Description |
|---|---|---|
RoundDown(int multipleOf = 10) |
int |
Rounds down to the nearest multiple (15 becomes 10). Negative values round toward zero. |
RoundUp(int multipleOf = 10, bool forceDifferentValue = false) |
int |
Rounds up to the next multiple (11 becomes 20). An exact multiple is returned unchanged unless forceDifferentValue is true, in which case multipleOf is added. |
RoundBest(int multipleOf = 10, bool forceDifferentValue = false) |
int |
Rounds to the nearest multiple: down when the remainder is less than multipleOf / 2, up otherwise. A remainder of exactly 1 always rounds down. An exact multiple is returned unchanged even when forceDifferentValue is true. |
TypeExtensions
public static class TypeExtensions — reflection helpers.
| Member | Extends | Description |
|---|---|---|
Implements<TInterface>() |
Type |
Returns true when TInterface is assignable from the type. TInterface must be a reference type. |
IsSimpleType() |
Type |
Returns true for value types, primitives, string, decimal, DateTime, DateTimeOffset, TimeSpan, Guid, and any type whose TypeCode is not Object. |
IsNumericType() |
Type |
Returns true for byte, sbyte, the 16-, 32- and 64-bit signed and unsigned integers, decimal, double and float, and for Nullable<T> of any of these. |
UpdateValues<T>(T newObject) |
T : IUpdateable |
Copies values from newObject into the current object and returns the current object. A property is copied when its new value is non-null and differs from the value on a freshly constructed T, or when newObject has a bool ShouldSerialize<PropertyName>() method that returns true. Simple types are assigned directly; properties whose type implements IUpdateable are merged recursively; other reference types (such as collections) are skipped. T needs a public parameterless constructor and settable properties. |
GetQueryStringBuilderKeyName() |
PropertyInfo |
Returns the key from [QueryStringBuilderKeyName], or null. Used by QueryStringBuilder.AddFromType. |
QueryStringBuilderIgnore() |
PropertyInfo |
Returns true when the property has [QueryStringBuilderIgnore]. |
GetCallingMethod(int frame = 1, bool excludeBegin = true) |
— (static) | Returns Namespace.Type.Method for the given stack frame (1 is the method that called GetCallingMethod). With excludeBegin, a leading CrispyWaffle. is removed. Returns "CrispyWaffle" when the frame has no method. |
XmlExtensions
public static class XmlExtensions — helpers for XmlReader, XmlDocument and XmlNode.
| Member | Extends | Description |
|---|---|---|
DuplicateReaders(out XmlReader cloneOne, out XmlReader cloneTwo) |
XmlReader |
Loads the reader into an XmlDocument, writes it to a MemoryStream and creates two readers (with IgnoreWhitespace) over that stream, then closes the original reader. Both clones share the same stream, so they are not independent: read one to the end before you start the other, and expect problems with documents larger than the reader buffer. |
GetFirstElementByTagName(string tagName) |
XmlDocument |
Returns the first element with the tag name, or null. |
GetNodeValue(string node) |
XmlNode |
Evaluates node as an XPath expression against the node and returns the inner text of the first match, or string.Empty. |
GetNodeValue(string[] nodes) |
XmlNode |
Evaluates each expression and joins the inner texts with a space. Missing nodes contribute an empty string. |
Examples
Parse user input safely
ToInt32 and TryToDateTime never throw, so they suit form and CSV input.
using System;
using CrispyWaffle.Extensions;
var page = "abc".ToInt32(); // 0
if ("31/12/2020".TryToDateTime(out var date)) // dd/MM/yyyy fallback
{
Console.WriteLine(date.ToString("yyyy-MM-dd")); // 2020-12-31
}
Console.WriteLine(1000m.ToMonetary(new System.Globalization.CultureInfo("en-US"))); // $1,000.00
Map enums to display and storage values
using CrispyWaffle.Attributes;
using CrispyWaffle.Extensions;
var label = OrderStatus.Pending.GetHumanReadableValue(); // "Waiting for payment"
var code = OrderStatus.Shipped.GetInternalValue(); // "SH"
var status = EnumExtensions.GetEnumByInternalValueAttribute<OrderStatus>("sh"); // OrderStatus.Shipped
public enum OrderStatus
{
[HumanReadable("Waiting for payment")]
[InternalValue("WP")]
Pending = 0,
[HumanReadable("Shipped")]
[InternalValue("SH")]
Shipped = 1,
}
Flatten an exception chain
using System;
using System.Collections.Generic;
using CrispyWaffle.Extensions;
using CrispyWaffle.Log.Providers;
try
{
throw new InvalidOperationException("Outer", new ArgumentException("Inner"));
}
catch (Exception ex)
{
var queue = ex.ToQueue(out var innerTypes); // Inner first, then Outer
var text = queue.GetMessages("Orders", new List<ILogProvider>());
Console.WriteLine(text);
}
Compare strings ignoring accents and case
using System.Text;
using CrispyWaffle.Extensions;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var distance = "Three".LevenshteinInvariantCulture("tree"); // 1
See also
- Attributes for
HumanReadable,InternalValueand the query string attributes - Utilities for
QueryStringBuilderandIUpdateable - Validations for the regular expressions used by the string helpers
- pt-BR internationalization for Brazilian-specific conversions