Skip to main content

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​

TopicWhat it covers
A tasteTen one-liners that show the style of the library.
No using directive neededMethods appear in IntelliSense as soon as the package is installed.
No dependenciesNothing else comes with the package.
Predictable null handlingWhat happens with null receivers and null arguments.
Ordinal comparisons, invariant formattingResults do not change with the server's culture.
No hidden clockTime-based methods take "now" as a parameter.
Safe for trimming and Native AOTWhich members warn in a trimmed or AOT app, and why.
Extension properties and operatorsC# 14 members such as text.IsBlank and defaults + overrides.
Map of the libraryOne 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.

TasteOfTheLibrary.cs
(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.

OrderSlug.cs
// 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.

note

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 ArgumentNullException or ArgumentOutOfRangeException, and the exception's ParamName is the name of the parameter.
  • String predicates (ContainsIgnoreCase, StartsWithIgnoreCase, EndsWithIgnoreCase, ...) return false for a null string instead of throwing.
  • Every signature carries nullable annotations, so the compiler tells you when a method can return null.
NullHandling.cs
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"
note

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.

InvariantOutput.cs
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.

CustomerAge.cs
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"
tip

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​

Strings and text​

Sequences and collections​

Objects​

Async and functions​

Time and numbers​

Types and system​

Where to go next​

See also​