Extension Methods
BooleanEx
Extensions for bool and bool?.
bool enabled = true;
// Execute an action only when true
enabled.RunIfTrue(() => Console.WriteLine("Feature is ON"));
// Execute an action only when false
enabled.RunIfFalse(() => Console.WriteLine("Feature is OFF"));
// Treat null as false
bool? nullable = null;
bool safe = nullable.ToFalseIfNull(); // false
ByteArrayEx
Extensions for byte[].
byte[] data = [0xCA, 0xFE, 0xBA, 0xBE];
string hex = data.ToHexString(); // "CAFEBABE"
string base64 = data.ToBase64(); // Base64-encoded string
byte[] sha256 = data.ComputeSha256(); // SHA-256 hash
// Reverse operations (static-like, called on string)
byte[] fromHex = "CAFEBABE".FromHexString();
byte[] fromBase64 = base64.FromBase64ToBytes();
ClaimsPrincipalEx
Extensions for ClaimsPrincipal (ASP.NET Core authentication).
ClaimsPrincipal user = httpContext.User;
string? role = user.GetClaimValue("role");
bool isAdmin = user.HasClaim("admin");
string? userId = user.GetUserId(); // "sub" or "nameidentifier"
string? email = user.GetEmail(); // "emailaddress" claim
string? display = user.GetDisplayName(); // "name" claim
ConfigurationEx
Extensions for IConfiguration (ASP.NET Core configuration).
IConfiguration config = builder.Configuration;
// Throws InvalidOperationException if missing
string connStr = config.GetRequiredValue("ConnectionStrings:Default");
// Returns fallback if missing
string env = config.GetValueOrDefault("Env", "development");
// Check existence
bool hasKey = config.HasKey("FeatureFlags:NewUI");
DateTimeEx
Extensions for DateTime, DateTime?, and date string parsing.
DateTime now = DateTime.UtcNow;
string date = now.ToFormattedDate(); // "dd/MM/yyyy"
string dateTime = now.ToFormattedDateTime(); // "dd/MM/yyyy HH:mm:ss"
DateTime next = now.AddWeeks(2);
DateTime clean = now.TruncateMilliseconds();
bool weekday = now.IsWeekday();
bool weekend = now.IsWeekend();
DateTime start = now.StartOfDay(); // 00:00:00
DateTime end = now.EndOfDay(); // 23:59:59.9999999
DateTime som = now.StartOfMonth(); // 1st of the month
DateTime eom = now.EndOfMonth(); // last moment of the month
bool between = now.IsBetween(start, end);
string rel = now.AddHours(-3).ToRelativeTime(); // "3 hours ago"
// Nullable overloads
DateTime? nullable = null;
string safe = nullable.ToFormattedDate(); // ""
string rel2 = nullable.ToRelativeTime(); // ""
Date string parsing:
DateTime parsed = "25/12/2025".FromFormattedDate(); // dd/MM/yyyy
bool ok = "25/12/2025".TryFromFormattedDate(out DateTime result);
DateTime parsed2 = "25/12/2025 14:30:00".FromFormattedDateTime();
bool ok2 = "nope".TryFromFormattedDateTime(out _); // false
Week/year/age/relative helpers:
DateTime now = DateTime.UtcNow;
DateTime mon = now.StartOfWeek(); // Monday 00:00 of current week
DateTime sun = now.EndOfWeek(); // last tick of Sunday
DateTime jan1 = now.StartOfYear(); // first tick of Jan 1
DateTime dec31 = now.EndOfYear(); // last tick of Dec 31
int age = birthDate.Age(); // whole years to now (UTC)
int ageAt = birthDate.AgeAt(asOf); // whole years to a reference date
bool today = someDate.IsToday();
bool past = someDate.IsInPast();
bool future = someDate.IsInFuture();
int daysLeft = expiresAt.DaysUntil(); // negative if past
bool leap = someDate.IsLeapYear();
long epochS = now.ToUnixTimeSeconds();
long epochMs = now.ToUnixTimeMilliseconds();
DateTimeOffsetEx
Mirrors every method in DateTimeEx for DateTimeOffset and DateTimeOffset?: ToFormattedDate, ToFormattedDateTime, AddWeeks, TruncateMilliseconds, IsWeekday, IsWeekend, StartOfDay, EndOfDay, StartOfMonth, EndOfMonth, IsBetween, ToRelativeTime, StartOfWeek, EndOfWeek, StartOfYear, EndOfYear, Age, AgeAt, IsToday, IsInPast, IsInFuture, DaysUntil, IsLeapYear, and the nullable overloads. Usage is identical — just substitute DateTimeOffset for DateTime.
DictionaryEx
Extensions for IDictionary<TKey, TValue>.
var dict = new Dictionary<string, int> { ["a"] = 1, ["b"] = 2 };
string json = dict.ToJson(); // JSON string
bool has = dict.HasKeyAndValue("a", 1); // true
bool any = dict.HasAnyKey("x", "a"); // true (has "a")
string qs = dict.ToQueryString(); // "a=1&b=2"
string dbg = dict.ToDebugString(); // "[a, 1] [b, 2]"
var other = new Dictionary<string, int> { ["a"] = 1, ["b"] = 2 };
bool equal = dict.IsDeepEqualTo(other); // true
// Merge (second wins on conflicts)
var merged = dict.Merge(new Dictionary<string, int> { ["b"] = 99, ["c"] = 3 });
// { a=1, b=99, c=3 }
// Get or lazily add
int val = dict.GetOrAdd("z", () => 42); // adds "z"=42, returns 42
// Add or update in a single call
int result = dict.AddOrUpdate("a", addValue: 1, (_, existing) => existing + 10);
// If "a" existed with value 1, result is 11
// Remove matching entries (returns count removed)
int removed = dict.RemoveWhere(kv => kv.Value > 50);
// Get with fallback (no mutation)
int safe = dict.GetValueOrDefault("missing", -1); // -1
// Nested dictionary helpers
var nested = new Dictionary<string, Dictionary<string, int>>();
nested.AddEntryToNestedDictionary("outer", "inner", 10);
nested.RemoveEntryFromNestedDictionary("outer", "inner");
EnumEx
Extensions for Enum values.
public enum Status
{
[Description("In Progress")]
InProgress,
[EnumMember(Value = "not_started")]
NotStarted
}
string desc = Status.InProgress.GetDescription(); // "In Progress"
string member = Status.NotStarted.GetEnumMemberValue(); // "not_started"
// Get any custom attribute
var attr = Status.InProgress.GetCustomAttribute<DescriptionAttribute>();
EnumerableEx
Extensions for IEnumerable<T>.
int[] numbers = [1, 2, 3, 4, 5];
// Null/empty checks
bool empty = numbers.IsNullOrEmpty(); // false
bool hasItems = numbers.IsNotNullOrEmpty(); // true
// Indexed ForEach
numbers.ForEach((item, index) => Console.WriteLine($"{index}: {item}"));
// ForEach with early exit (return false to stop)
numbers.ForEach((item, _) => { Console.WriteLine(item); return item < 3; });
// Batch into groups of N
IEnumerable<IEnumerable<int>> batches = numbers.Batch(2);
// [[1,2], [3,4], [5]]
// Partition by predicate
var (evens, odds) = numbers.Partition(n => n % 2 == 0);
// evens: [2,4], odds: [1,3,5]
// Interleave two sequences
int[] a = [1, 3, 5], b = [2, 4, 6];
var interleaved = a.Interleave(b); // [1,2,3,4,5,6]
// Running accumulator
var running = numbers.Scan(0, (acc, x) => acc + x); // [1,3,6,10,15]
// Sliding window
var windows = numbers.Window(3); // [[1,2,3],[2,3,4],[3,4,5]]
// Shuffle (optional Random parameter)
var shuffled = numbers.Shuffle();
// Fallback if empty
int[] result = Array.Empty<int>().FallbackIfEmpty(99).ToArray(); // [99]
// Consecutive pairs
var pairs = numbers.Pairwise((a, b) => $"{a}->{b}");
// ["1->2","2->3","3->4","4->5"]
// Tag first/last
var tagged = numbers.TagFirstLast((item, isFirst, isLast) =>
$"{item} first={isFirst} last={isLast}");
// Remove nulls
string?[] mixed = ["hello", null, "world"];
IEnumerable<string> clean = mixed.WhereNotNull(); // ["hello","world"]
// Duplicate and predicate checks
numbers.HasDuplicates(); // false
numbers.None(n => n > 100); // true
// Infinite cycling (caller must limit)
int[] taken = numbers.Cycle().Take(8).ToArray(); // [1,2,3,4,5,1,2,3]
// Flatten nested sequences
IEnumerable<int[]> nested = [[1, 2], [3], [4, 5]];
IEnumerable<int> flat = nested.Flatten(); // [1,2,3,4,5]
EnvironmentEx
Extensions for IHostEnvironment (ASP.NET Core).
IHostEnvironment env = app.Environment;
bool local = env.IsLocal(); // env.EnvironmentName == "local"
bool test = env.IsTest(); // "test"
bool uat = env.IsUat(); // "uat"
ExceptionEx
Extensions for Exception and ExceptionBase.
try { /* ... */ }
catch (Exception ex)
{
// Full detail string including inner exceptions and stack traces
string detail = ex.ToDetailString();
// Flatten aggregate/inner exceptions into a single list
IEnumerable<Exception> all = ex.Flatten();
}
// Convert ExceptionBase to ErrorResponse
try { throw new BadRequestException("Invalid input"); }
catch (ExceptionBase ex)
{
ErrorResponse response = ex.ToErrorResponse();
// response.Message = "Invalid input"
// response.MessageHeader = "Bad Request"
}
GuardEx
Argument guard extensions for any value.
public void Process(string name, int age, Guid id, IList<string> tags)
{
name.ThrowIfNull(); // ArgumentNullException
name.ThrowIfNullOrWhiteSpace(); // ArgumentException
name.ThrowIfInvalidFormat(@"^[A-Z]"); // FormatException
age.ThrowIfNegative(); // ArgumentOutOfRangeException
age.ThrowIfNegativeOrZero();
age.ThrowIfZero();
age.ThrowIfOutOfRange(1, 120); // must be in [1, 120]
age.ThrowIfLessThan(18); // ArgumentOutOfRangeException if < 18
age.ThrowIfGreaterThan(65); // ArgumentOutOfRangeException if > 65
id.ThrowIfDefault(); // ArgumentException (Guid.Empty)
tags.ThrowIfEmpty(); // ArgumentException (empty collection)
// Custom predicate guard
age.ThrowIf(a => a > 200, "Age cannot exceed 200");
}
GuidEx
Extensions for Guid.
Guid id = Guid.NewGuid();
bool isEmpty = Guid.Empty.IsEmpty(); // true
string short = id.ToShortString(); // first 8 hex chars
HttpEx
Extensions for HttpResponseMessage and HttpRequest (ASP.NET Core).
// Deserialize an HTTP response body
HttpResponseMessage response = await httpClient.GetAsync("/api/users/1");
User user = await response.EnsureObjectAsync<User>();
// Case-insensitive deserialization
User user2 = await response.EnsureObjectAsync<User>(caseInsensitive: true);
// Read raw request body (ASP.NET Core middleware)
app.Use(async (context, next) =>
{
string body = await context.Request.GetRawBodyAsync();
await next();
});
JsonEx
Extensions for JSON serialization on any object or string.
var order = new { Id = 1, Total = 42.50 };
// Serialize
string json = order.ToJson(); // compact
string pretty = order.ToJson(indented: true); // pretty-printed
// Deserialize
Order parsed = json.FromJson<Order>();
// Safe try-parse
if (json.TryFromJson<Order>(out Order? result))
Console.WriteLine(result.Id);
ListEx
Extensions for IList<T>, List<T>, and List<string>.
var list = new List<string>();
// Add only if not null
string? maybe = GetValueOrNull();
list.AddIfNotNull(maybe);
// Batch add
list.AddRange("a", "b", "c");
// Batch add, skipping nulls
list.AddRangeIfNotNull("x", null, "y"); // adds "x" and "y"
// Fluent Add (returns the list)
var built = new List<int>().Add(1, 2, 3).Add(4);
// String-specific: case-insensitive contains, no-duplicate add
var tags = new List<string> { "CSharp" };
bool has = tags.ContainsIgnoreCase("csharp"); // true
tags.AddIfNotExists("CSharp"); // no-op (already exists)
tags.AddRangeNoDuplicates(["dotnet", "CSharp"]); // adds only "dotnet"
NumericEx
Extensions for numbers (INumber<T>), int, long, and double.
int n = 42;
bool zero = 0.IsZero(); // true
bool positive = n.IsPositive(); // true
bool negative = (-1).IsNegative(); // true
// Human-readable byte sizes
long bytes = 1_073_741_824L;
string size = bytes.ToHumanByteSize(); // "1.00 GB"
string si = bytes.ToHumanByteSize(useSiUnits: true); // "1.07 GB"
// Ordinal suffix
string ord = 3.ToOrdinal(); // "3rd"
// Fluent TimeSpan builders
TimeSpan ts = 5.Seconds(); // TimeSpan.FromSeconds(5)
TimeSpan d = 2.5.Hours(); // TimeSpan.FromHours(2.5)
// Also: .Days(), .Minutes(), .Milliseconds()
// Clamp, range test (INumber<T>)
15.ClampTo(min: 5, max: 10); // 10
7.IsBetween(5, 10); // true (inclusive)
// Rounding and percentages (double / decimal)
3.14159.RoundTo(2); // 3.14
25.0.PercentageOf(200.0); // 12.5
ObjectEx
Extensions for object? and generic T.
object value = "42";
bool isNum = value.IsNumeric(); // true
double? dbl = value.ToNullableDouble(); // 42.0
int? i = value.ToNullableInteger(); // 42
bool? b = "true".ToNullableBoolean(); // true
DateTime? dt = "2025-01-01".ToNullableDateTime("yyyy-MM-dd");
// Create StringContent for HTTP POST
StringContent content = new { Name = "test" }.ToStringContent();
// Pipe (execute side-effect, returns the original value)
var user = GetUser().Pipe(u => Console.WriteLine(u.Name));
SemaphoreSlimEx
Extensions for SemaphoreSlim.
var semaphore = new SemaphoreSlim(1, 1);
// Async lock with RAII-style disposal
await using (await semaphore.LockAsync())
{
// exclusive access here
}
// With cancellation token
await using (await semaphore.LockAsync(cancellationToken))
{
// ...
}
ServiceProviderEx
Extensions for IServiceProvider (scoped service access).
IServiceProvider sp = app.Services;
// Async scoped access with return value
int count = await sp.UseScopedAsync<IUserRepository, int>(
async repo => await repo.CountAsync());
// Async scoped access without return value
await sp.UseScopedAsync<IEmailService>(
async svc => await svc.SendWelcomeAsync(userId));
// Synchronous scoped access
sp.UseScoped<ICacheService>(cache => cache.Clear());
SetEx
Extensions for ISet<T>.
ISet<int> set = new HashSet<int> { 1, 2, 3 };
// Compare a set against a JSON-serialized representation
bool match = set.EqualsSerializedSet("[1,2,3]"); // true (order-independent)
SpanEx
Extensions for ReadOnlySpan<char>.
ReadOnlySpan<char> span = " Hello, World! ".AsSpan();
bool contains = span.ContainsIgnoreCase("hello"); // true
bool numeric = "12345".AsSpan().IsNumeric(); // true
ReadOnlySpan<char> trimmed = span.SafeTrim(); // "Hello, World!"
StreamEx
Extensions for Stream.
Stream stream = GetFileStream();
byte[] bytes = await stream.ToByteArrayAsync();
string text = await stream.ToStringAsync(); // UTF-8
string latin = await stream.ToStringAsync(Encoding.Latin1);
MemoryStream copy = await stream.ToMemoryStreamAsync();
// With cancellation
byte[] data = await stream.ToByteArrayAsync(cancellationToken);
StringEx
Extensions for string, string?, and byte[]?.
string text = "Hello, World!";
// Encoding
byte[] bytes = text.ToByteArray(); // UTF-8
string base64 = text.ToBase64();
string decoded = base64.FromBase64();
// Cleanup
string clean = "h3ll0!@#w0rld".RemoveSpecialCharacters(); // "h3ll0w0rld"
string trimmed = ((string?)null).SafeTrim(); // ""
// Splitting
string[] parts = "one::two::three".RegexSplit("::");
// Comparisons (case-insensitive)
bool eq = "ABC".EqualsIgnoreCase("abc"); // true
bool sw = "Hello".StartsWithIgnoreCase("hel"); // true
bool ew = "Hello".EndsWithIgnoreCase("LLO"); // true
bool ct = "Hello".ContainsIgnoreCase("ell"); // true
// With auto-trim
bool eqt = " ABC ".EqualsIgnoreCaseWithTrim("abc"); // true
// Enum parsing
DayOfWeek day = "Monday".ToEnum<DayOfWeek>();
// Validation
bool isEmail = "user@example.com".IsWellFormedEmailAddress(); // true
bool isNum = "42".IsNumeric(); // true
bool isGuid = Guid.NewGuid().ToString().IsGuid(); // true
bool blank = ((string?)null).IsNullOrWhiteSpace(); // true
// Truncation & masking
string trunc = "Hello, World!".Truncate(5); // "Hello..."
string masked = "4111111111111111".Mask(4); // "************1111"
// Case conversion & humanization
string snake = "HelloWorld".ToSnakeCase(); // "hello_world"
string kebab = "HelloWorld".ToKebabCase(); // "hello-world"
string camel = "hello_world".ToCamelCase(); // "helloWorld"
string human = "order_line_item".Humanize(); // "Order line item"
// Slug & reverse
string slug = "Hello, World! 2025".ToSlug(); // "hello-world-2025"
string rev = "abc".Reverse(); // "cba"
// byte[]? -> string
byte[]? raw = GetBytes();
string str = raw.FromByteArray(); // UTF-8 decode, or "" if null
Transformations and formatting:
"my_property_name".ToPascalCase(); // "MyPropertyName" (also handles camelCase, kebab-case)
"hello world".ToTitleCase(); // "Hello World"
"<p>hi <b>there</b></p>".StripHtml(); // "hi there"
"café naïve".RemoveDiacritics(); // "cafe naive"
"hello world".WordCount(); // 2
"ab".Repeat(3); // "ababab"
"world".EnsureStartsWith("hello-"); // "hello-world" (or as-is if already present)
"file".EnsureEndsWith(".txt"); // "file.txt"
// Multiple replacements in one pass
"foo and baz".ReplaceMultiple(new Dictionary<string, string>
{
["foo"] = "bar",
["baz"] = "qux",
}); // "bar and qux"
TaskEx
Extensions for Task, Task<T>, and static async helpers.
// Sequential execution (respects order)
await TaskEx.WhenAllSequentialAsync(
() => StepOneAsync(),
() => StepTwoAsync(),
() => StepThreeAsync());
// Sequential with results
IEnumerable<int> results = await TaskEx.WhenAllSequentialAsync(
() => ComputeAAsync(),
() => ComputeBAsync());
// Retry with exponential backoff
string html = await TaskEx.RetryAsync(
() => httpClient.GetStringAsync("https://example.com"),
maxRetries: 3,
delay: TimeSpan.FromSeconds(1),
exponentialBackoff: true);
// Retry with exception filter
await TaskEx.RetryAsync(
() => SaveAsync(),
maxRetries: 5,
retryWhen: ex => ex is TimeoutException);
// Timeout
string data = await LongRunningAsync()
.WithTimeoutAsync(TimeSpan.FromSeconds(10));
// Error callback
string safe = await RiskyAsync()
.OnFailureAsync(ex => logger.LogError(ex, "Failed"));
// Fire and forget (swallows exceptions, optional handler)
SendAnalyticsAsync().FireAndForget(ex => logger.LogWarning(ex, "Analytics failed"));
// Attach a cancellation token to a task that does not natively accept one.
// Throws OperationCanceledException if the token fires before the task completes.
int value = await someTask.WithCancellationAsync(cancellationToken);
TimeSpanEx
Extensions for TimeSpan.
TimeSpan elapsed = TimeSpan.FromHours(2) + TimeSpan.FromMinutes(30);
string human = elapsed.ToHumanReadable(); // "2 hours 30 minutes"
// Control decimal precision
TimeSpan precise = TimeSpan.FromSeconds(61.456);
string p = precise.ToHumanReadable(decimalPlaces: 2); // "1 minute 1.46 seconds"
// Clock formatting (HH:MM:SS) — hours are not capped at 24
new TimeSpan(1, 3, 10, 0).ToClockString(); // "27:10:00"
TimeSpan.FromSeconds(-65).ToClockString(); // "-00:01:05"
TimeSpan.FromSeconds(1).IsPositive(); // true
TimeSpan.FromSeconds(-1).IsNegative(); // true
UriEx
Extensions for Uri.
var uri = new Uri("https://user:pass@example.com:8080/api/v1?q=test#section");
string dump = uri.DumpProperties();
// Multi-line string with Scheme, Host, Port, Path, Query, Fragment, etc.
// Path and query manipulation
Uri api = new Uri("https://example.com/api/").AppendPath("users");
// https://example.com/api/users
IReadOnlyDictionary<string, string> query = uri.GetQueryParameters();
// { "q": "test" }
Uri withQuery = new Uri("https://example.com/api")
.WithQueryParameter("page", "2")
.WithQueryParameter("size", "50");
// https://example.com/api?page=2&size=50
XmlSerializerEx
Extensions for XML serialization.
// Serialize any object to XML
var item = new Product { Id = 1, Name = "Widget" };
string xml = item.Serialize();
// Deserialize from XML string
Product restored = xml.Deserialize<Product>();
EnumerableExAsync
Asynchronous extension methods for IEnumerable<T>. For synchronous extensions, see EnumerableEx.
var userIds = new[] { 1, 2, 3, 4, 5 };
// Sequential async projection
var users = await userIds.SelectAsync(id => GetUserAsync(id));
// Parallel async projection with concurrency limit
var enrichedUsers = await users.SelectAsyncParallel(
user => EnrichUserDataAsync(user),
maxConcurrency: 10);
// Async filtering
var activeUsers = await users.WhereAsync(user => IsActiveAsync(user));
// Async iteration
await users.ForEachAsync(user => ProcessUserAsync(user));
// Async aggregation
var totalScore = await scores.AggregateAsync(
0,
async (sum, score) => sum + await CalculateAsync(score));
// Async predicates
bool hasAdmin = await users.AnyAsync(user => IsAdminAsync(user));
bool allActive = await users.AllAsync(user => IsActiveAsync(user));
FuncEx
Extensions for Func<T> and Action delegates providing memoization, debouncing, and throttling.
// Memoization - cache expensive function results
Func<int, int> fibonacci = null!;
fibonacci = n => n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
var memoized = fibonacci.Memoize();
var result1 = memoized(40); // Calculated
var result2 = memoized(40); // Retrieved from cache (instant!)
// Debounce - delay execution until calls stop
Action search = () => PerformSearch();
var debouncedSearch = search.Debounce(TimeSpan.FromMilliseconds(300));
// In event handler (e.g., search-as-you-type)
textBox.TextChanged += (s, e) => debouncedSearch();
// Only executes after user stops typing for 300ms
// Throttle - limit execution frequency
Action updateMetrics = () => SendMetricsToServer();
var throttled = updateMetrics.Throttle(TimeSpan.FromSeconds(5));
// Called many times, but only executes once per 5 seconds
for (int i = 0; i < 100; i++)
throttled();
// Lazy initialization
Func<ExpensiveResource> factory = () => new ExpensiveResource();
Lazy<ExpensiveResource> lazy = factory.ToLazy();
// Resource only created on first access
var resource = lazy.Value;
RegexEx
Regex extension methods on string that avoid constructing a Regex manually.
"abc123".RegexIsMatch(@"[a-z]+\d+"); // true
"abc123def456".RegexMatch(@"\d+"); // "123"
"abc123def456".RegexMatches(@"\d+"); // ["123", "456"]
"a1 b2 c3".RegexReplace(@"\d", m => (int.Parse(m.Value) * 10).ToString());
// "a10 b20 c30"
"abc123".RegexReplace(@"\d+", "X"); // "abcX"
CancellationTokenEx
// Convert a token to a Task that completes (cancelled) when the token fires
Task task = cancellationToken.AsTask();
// Create a linked token that also cancels after a timeout (caller disposes)
using CancellationTokenSource linked = outerToken.WithTimeout(TimeSpan.FromSeconds(30));