Overview
Masterly.Extensions.Core is one NuGet package of C# extension methods for the types you use every day: strings, sequences, dictionaries, objects, dates, numbers, tasks, types, enums and exceptions. Reach for it when you keep writing the same small helper in every project, such as a slug, a page of results, a retry loop or a "3 hours ago" label.
It targets netstandard2.1, so it runs on every .NET Core 3.0+ and .NET 5+ app. It also ships a separate build for .NET 10 that is safe to trim and to compile with Native AOT.
At a glance
| Topic | What it covers |
|---|---|
| A taste | Ten one-liners that show the style of the library. |
| No using directive needed | Methods appear in IntelliSense as soon as the package is installed. |
| No dependencies | Nothing else comes with the package. |
| Predictable null handling | What happens with null receivers and null arguments. |
| Ordinal comparisons, invariant formatting | Results do not change with the server's culture. |
| No hidden clock | Time-based methods take "now" as a parameter. |
| Safe for trimming and Native AOT | Which members warn in a trimmed or AOT app, and why. |
| Extension properties and operators | C# 14 members such as text.IsBlank and defaults + overrides. |
| Map of the library | One card per guide page. |
A taste
C# binds await, foreach, deconstruction and LINQ query syntax to methods by name, and an extension method with the right name counts. The library uses this so that tuples of tasks, ranges, arrays and durations work with those keywords. The rest are plain methods with clear names.
(User user, List<Order> orders) = await (GetUserAsync(id), GetOrdersAsync(id)); // concurrent
await 2.Seconds(); // a 2-second delay
(string name, string domain) = "ahmad@example.com".Split('@'); // → "ahmad", "example.com"
int[] squares = (from i in 0..10 where i % 2 == 0 select i * i).ToArray();
// → [0, 4, 16, 36, 64]
string route = "getHTTPResponse".ToKebabCase(); // → "get-http-response"
TimeSpan timeout = "1d 2h 30m".ToTimeSpan(); // → 1.02:30:00
long quota = "10 MB".ToByteCount(); // → 10485760
int? pageSize = "abc".TryParse<int>(); // → null
OrderStatus? status = "pendng".ToClosestEnum<OrderStatus>(); // → OrderStatus.Pending
string due = invoice.DueDate.ToRelativeTime(now); // → "in 3 days"
Each line has its own guide: Await anything, foreach and deconstruction, String casing, String parsing, Fuzzy and natural matching and Dates.
Design principles
Every method in the library follows the same rules. Once you know them, you can predict how a method you have not used yet behaves. The Conventions page lists them in full.
No using directive needed
Each extension class is declared in the namespace of the type it extends: System, System.Collections.Generic, System.Linq, System.Threading.Tasks and so on. Those namespaces are already imported in a normal project, so the methods appear as soon as you install the package. There is no using Masterly... line to add.
// No extra using directive: ToSlug is declared in the System namespace.
string slug = "Héllo, Wörld! 2024".ToSlug(); // → "hello-world-2024"
With InvariantGlobalization enabled (the default for Native AOT console apps), the accents are kept: "héllo-wörld-2024". See RemoveDiacritics.
A few reflection helpers live in System.Reflection: GetPropertyValue, SetPropertyValue, and the GetAttribute<T> / GetAttributes<T> / HasAttribute<T> family on MemberInfo. Add using System.Reflection; if your file does not have it. See Types and reflection.
The library also avoids method names that .NET itself ships, such as Chunk, DistinctBy, Index, CountBy and Shuffle. A few names exist in other libraries: FluentAssertions has As<T>(), and System.Interactive has Scan, DistinctUntilChanged and IsEmpty. If a call becomes ambiguous, call the method statically, for example EnumerableExtensions.Scan(prices, (sum, p) => sum + p), or use a using alias.
No dependencies
The package has no third-party dependencies. The .NET 10 build uses only the base class library. The netstandard2.1 build references System.Text.Json, the Microsoft package that newer runtimes include. JSON-based members such as ToJsonString, DeepClone and ToSafeJson use it.
Predictable null handling
The rules for null are the same everywhere:
- An invalid argument throws
ArgumentNullExceptionorArgumentOutOfRangeException, and the exception'sParamNameis the name of the parameter. - String predicates (
ContainsIgnoreCase,StartsWithIgnoreCase,EndsWithIgnoreCase, ...) returnfalsefor anullstring instead of throwing. - Every signature carries nullable annotations, so the compiler tells you when a method can return
null.
string? middleName = null;
bool hasPrefix = middleName.StartsWithIgnoreCase("van"); // → false: a null receiver is not an error
"Invoice".Left(20); // throws ArgumentOutOfRangeException, ParamName → "len"
(0..10).Step(0); // throws ArgumentOutOfRangeException, ParamName → "step"
A null value to look for is a bug in the calling code, not in the data, so it still throws. "Hello".StartsWithIgnoreCase((string)null!) throws ArgumentNullException with ParamName "toCheck".
Ordinal comparisons, invariant formatting
String comparisons are ordinal and formatting uses the invariant culture, unless the method takes a CultureInfo, a StringComparison or a useCurrentCulture flag. The same code produces the same text on a German server and on an American laptop. Dates in query strings and CSV are written in ISO 8601.
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("de-DE"); // here 1234.5 prints as "1234,5"
Dictionary<string, object?> filter = new Dictionary<string, object?>
{
["total"] = 1234.5m,
["since"] = new DateTime(2026, 5, 17),
};
string query = filter.ToQueryString(); // → "total=1234.5&since=2026-05-17T00%3A00%3A00.0000000"
string followers = 1_300_000.ToCompactString(); // → "1.3M"
When you want the current culture, say so. ToKebabCase(useCurrentCulture: true) is one example, and CultureInfo.GetCultureInfo("ar-JO").Use() switches the culture for a using block (see I/O and system).
No hidden clock
No method reads DateTime.Now or DateTime.UtcNow. A method that needs "now" or "today" takes it as a parameter. In production you pass the real time. In a test you pass a fixed one, so the result never depends on when the test runs.
DateTime today = new DateTime(2026, 5, 17);
int age = new DateTime(1990, 8, 1).Age(today); // → 35
DateTime due = new DateTime(2026, 5, 15).AddBusinessDays(3); // Friday + 3 business days → Wednesday 2026-05-20
string label = new DateTime(2026, 5, 20).ToRelativeTime(today); // → "in 3 days"
In application code, pass the value from an injected TimeProvider (timeProvider.GetUtcNow().UtcDateTime) rather than DateTime.UtcNow. Your own code then stays as testable as the library.
Safe for trimming and Native AOT
The .NET 10 build is marked IsAotCompatible, and the trim analyzers run on every build of the library. Most members use no reflection at all. The members that do, such as DeepClone, ToJsonString, FromJson, ToSafeJson, ContentHash, CompareProperties, MapTo, UpdateFrom, SortBy, WithQuery, FormatWith and GetPropertyValue, carry [RequiresUnreferencedCode] / [RequiresDynamicCode]. A trimmed or AOT-published app gets a warning at exactly the call sites that need attention, and nowhere else.
Members that reflect over a type argument, such as ToTable, ToCsv and ToObject<T>, annotate T with [DynamicallyAccessedMembers], so they keep working after trimming. The full list is in Trimming and AOT.
Extension properties and operators
A few members are C# 14 extension members: the properties text.IsBlank and guid.IsEmpty, static members such as Guid.NewShortId() and TimeSpan.FromWeeks(2), and the operators "-" * 40 and defaults + overrides. They compile to ordinary static methods, so the package runs on every supported runtime. The new syntax needs a C# 14 compiler (the .NET 10 SDK). Older compilers still see every classic x.Method() extension. See Extension members.
Map of the library
Select an area to open its guide.
Language features
- Await anythingAwait a tuple of tasks, a list of tasks, a TimeSpan, a CancellationToken, a SemaphoreSlim or a Process.
- foreach and deconstructionforeach over 0..10 and date ranges, LINQ query syntax on ranges, and deconstruction of lists, dates and groupings.
- Collection initializersInitializer syntax for Stack, Queue and LinkedList, tuple entries for dictionaries, and spreading a list.
- Extension membersC# 14 properties, static members and operators on BCL types, such as text.IsBlank and Guid.NewShortId().
Strings and text
- String predicatesCase-insensitive Contains, StartsWith and EndsWith with several candidates, and other true/false checks.
- String casingkebab-case, snake_case, camelCase, PascalCase, CONSTANT_CASE, Title Case and slugs, with acronyms handled.
- String manipulationTruncate, mask, wrap, indent, dedent, center, take text between markers and remove diacritics.
- String parsingTryParse to a nullable value, durations and byte sizes as people type them, and templates in reverse.
- Text and humanizePlurals, quantities, numbers as words, relative times, reading time, templates and line diffs.
- Encoding and validationBase64, Base64Url, hex, SHA-256 and HMAC, and checks for email, URL, IBAN, card numbers and JSON.
- Fuzzy and natural"Did you mean" lookups for strings, enums and dictionary keys, and natural sort order: file2 before file10.
Sequences and collections
- Querying sequencesWhereIf, None, TakeUntil, IsSorted, Frequencies, MinMax and other questions about a sequence.
- Transforming sequencesScan, Window, Pairwise, Partition, Interleave, Flatten, combinations, permutations and full outer joins.
- StatisticsMedian, percentiles, standard deviation, mode, outlier removal and a linear trend.
- Diff and dependenciesCompare two lists by key into added, removed and changed items, and sort items by their dependencies.
- Paging and sortingToPage for lists and IQueryable, and SortBy("name desc") with an allow-list of sortable properties.
- DictionariesGetOrDefault, GetOrAdd, AddOrUpdate, Increment, Merge, Invert, RemoveWhere and query strings.
- Lists and collectionsInsert, move and replace items in a list, AddIfNotContains and RemoveAll on any collection.
- Console outputTables, Markdown tables, trees, sparklines, bar charts, histograms, timelines and boxes as text.
Objects
- ObjectsType checks with Is and As, conversion with To, IsIn, If, AsDictionary and query strings from objects.
- Compare, clone and mapList changed properties, deep clone, map an entity to a DTO and apply a PATCH request.
- JSON and safe loggingJSON with System.Text.Json, canonical JSON, content hashes, and JSON with passwords and tokens masked.
Async and functions
- TasksBounded parallelism, retry with backoff, timeouts, fallbacks, Then/Catch chaining and fire-and-forget.
- FunctionsMemoize, Once, Debounce, Throttle, SingleFlight, Latest, Pipe, Compose and Negate.
Time and numbers
- DatesStart and end of day, week, month and year, business days, ages, rounding and Unix time.
- Durations5.Minutes(), 3.Days().Before(now), rounding a TimeSpan, and durations as text people read.
- NumbersOrdinals, file sizes, compact numbers, words, Roman numerals, fractions, clamping and rescaling.
- RandomRandom items, strings, enums, dates and Gaussian values, all repeatable with a seeded Random.
Types and system
- Types and reflectionFriendly type names, generic type checks, record and nullable checks, attributes and Guid short ids.
- EnumsDescriptions, flags, IsDefined, WithFlag, WithoutFlag and HasAnyFlag.
- Exceptions and disposablesAll messages, root cause, inner exceptions by type, Rethrow, AsDisposable and DisposeAll.
- I/O and systemRead a TextReader with foreach, read streams, await a process, build URLs and switch culture.
Where to go next
- InstallationAdd the package and check which build your app uses.
- Quick startA short tour of the most useful methods.
- Real-world examplesLonger examples that combine several areas of the library.
- API referenceEvery public type and member, grouped by area.
See also
- Installation
- Conventions: the rules above, with every exception to them.
- Upgrading from 1.x: what changed in 2.0.
- Trimming and AOT