Quick Start
This page is a five-minute tour. Each step is a small snippet from a different area of the library, followed by the output it produces. Run them one by one, then follow the link at the end of each step to the full guide.
At a glance
| Method | What it does |
|---|---|
ToKebabCase | Converts "getHTTPResponse" to "get-http-response". |
ToSlug | Builds a URL slug: lower-case, accents removed, words joined by -. |
Mask | Hides the middle of a card number or token. |
ToQuantity | Writes a count with the right word form: "3 invoices", "0 people". |
TryParse<T> | Parses text to a value type, or returns null instead of throwing. |
ToTimeSpan | Parses a human duration such as "1d 2h 30m". |
ToByteCount | Parses a human size such as "10 MB" into bytes. |
Scan | Returns every intermediate result of an accumulation, such as running totals. |
Window | Slides a fixed-size window over a sequence, for moving averages. |
ToPage | Takes one page of items and returns it with its totals as a Page<T>. |
await (task1, task2) | Awaits a tuple of tasks concurrently and returns a tuple of results. |
Milliseconds / Seconds | Creates a TimeSpan from a number. A TimeSpan can be awaited as a delay. |
Timed | Awaits a task and also reports how long it took. |
WithFallback | Returns a fallback value when a task fails or is too slow. |
DeepClone | Makes a deep copy of an object graph. |
CompareProperties | Lists the properties that differ between two objects, with dotted paths. |
ToRelativeTime | Describes a date relative to "now": "5 minutes ago", "yesterday". |
StartOfWeek | Gets midnight on the first day of the week (Monday by default). |
AddBusinessDays | Adds working days, skipping weekends. |
Days / Hours / Minutes | Creates a TimeSpan from a number. |
ToHumanString | Describes a duration in words: "1 day, 2 hours, 30 minutes". |
ToTable | Renders a list of objects as a box-drawn text table. |
ToSparkline | Draws numbers as a one-line chart of block characters. |
ToBarChart | Draws a horizontal bar chart, one line per item. |
Before you start
Create a console app and add the package:
dotnet new console -n QuickTour
cd QuickTour
dotnet add package Masterly.Extensions.Core
Paste each snippet into Program.cs and run dotnet run. You do not need a using directive for the library. Each extension class lives in the namespace of the type it extends (System, System.Linq, System.Collections.Generic, System.Threading.Tasks), and the default console template already imports those namespaces. See Installation for supported frameworks and Conventions for the rules every method follows.
The snippets use top-level statements. When a snippet declares classes or local functions, keep them at the bottom of Program.cs, after the statements.
Step 1: Strings
Turn identifiers, titles and sensitive values into text you can put in a URL, a log or a UI.
string route = "getHTTPResponse".ToKebabCase(); // → "get-http-response"
string slug = "Héllo, Wörld! 2024".ToSlug(); // → "hello-world-2024"
string card = "4111111111111111".Mask(); // → "************1111"
string invoices = "invoice".ToQuantity(3); // → "3 invoices"
string people = "person".ToQuantity(0); // → "0 people"
ToKebabCase keeps an acronym such as HTTP together as one word. Mask keeps the last 4 characters by default and returns a string of the same length. The keepStart, keepEnd and maskChar parameters change that. ToQuantity uses the singular for 1 and -1 and the English plural for every other count, and writes the count without culture-specific digit grouping.
ToKebabCase returns null for a null receiver. ToSlug, Mask and ToQuantity throw ArgumentNullException for null. ToSlug removes accents only when globalization is not in invariant mode: with InvariantGlobalization (or DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true, common in Docker images) it returns "héllo-wörld-2024".
Learn more: String casing, String manipulation, Text and humanize.
Step 2: Parsing
Read values that people type, or that arrive in a query string, without a try/catch.
int? pageSize = " 42 ".TryParse<int>(); // → 42
int? invalid = "abc".TryParse<int>(); // → null
int size = "".TryParse<int>() ?? 20; // → 20
TimeSpan cacheFor = "1d 2h 30m".ToTimeSpan(); // → 1.02:30:00
long uploadLimit = "10 MB".ToByteCount(); // → 10485760
TryParse<T> works for numbers, bool, Guid, DateTime, DateTimeOffset, TimeSpan, char, enums and any value type with a TypeConverter. It returns null for null, empty, white-space or invalid text. Parsing uses the invariant culture, so "1.5" means one and a half on every machine. ToByteCount uses 1024-based units.
ToTimeSpan and ToByteCount throw FormatException when the text is not a duration or a size. Use them on trusted input such as configuration, or catch the exception at the boundary.
Learn more: String parsing.
Step 3: Collections
Compute running totals and moving averages, and page a result for an API.
int[] dailySales = { 100, 200, 300, 400, 500 };
List<int> runningTotals = dailySales.Scan((total, sale) => total + sale).ToList();
// → [100, 300, 600, 1000, 1500]
List<double> movingAverage = dailySales.Window(3).Select(window => window.Average()).ToList();
// → [200, 300, 400]
IEnumerable<int> orderIds = Enumerable.Range(1, 45);
Page<int> page = orderIds.ToPage(pageNumber: 2, pageSize: 20);
int first = page.Items[0]; // → 21
int count = page.Items.Count; // → 20
int totalPages = page.TotalPages; // → 3
bool hasNext = page.HasNext; // → true
Window(3) returns only full windows, so five items give three windows. ToPage takes a 1-based page number. A page past the end comes back with no items and the real TotalCount. There is also a ToPage overload for IQueryable<T>, so with Entity Framework the paging runs in the database.
Window throws ArgumentOutOfRangeException when the size is below 1. ToPage throws it when pageNumber or pageSize is below 1.
Learn more: Transforming sequences, Paging and sorting.
Step 4: Tasks
Run two calls at the same time, time a call, and keep a page fast when a service is slow.
(string userName, int orderCount) = await (GetUserNameAsync(7), CountOrdersAsync(7));
// userName → "Ahmad"
// orderCount → 3
(string report, TimeSpan elapsed) = await BuildReportAsync().Timed();
// report → "Q2 report"
// elapsed → about 250 ms (it varies from run to run)
decimal price = await GetPriceAsync().WithFallback(0m, 200.Milliseconds());
// → 0 (the price service takes 5 seconds, so the fallback is used)
static async Task<string> GetUserNameAsync(int userId)
{
await 100.Milliseconds();
return "Ahmad";
}
static async Task<int> CountOrdersAsync(int userId)
{
await 150.Milliseconds();
return 3;
}
static async Task<string> BuildReportAsync()
{
await 250.Milliseconds();
return "Q2 report";
}
static async Task<decimal> GetPriceAsync()
{
await 5.Seconds();
return 9.99m;
}
Awaiting a tuple of tasks is Task.WhenAll with typed results. Both calls run concurrently, so the first line takes about 150 ms, not 250 ms. If one task fails, the await throws that task's exception. await 100.Milliseconds() works because the library makes a TimeSpan awaitable as a delay.
Timed measures from the moment you call it, so call it directly on the task as it starts, as shown.
Learn more: Await anything, Tasks.
Step 5: Objects
Copy an object, change the copy, and list what changed, for example to write an audit log entry.
Customer before = new Customer
{
Name = "Ahmad",
Email = "a@x.com",
Address = new Address { City = "Amman" },
};
Customer after = before.DeepClone();
after.Email = "b@x.com";
after.Address.City = "Irbid";
foreach (PropertyChange change in before.CompareProperties(after))
{
Console.WriteLine(change);
}
string originalCity = before.Address.City; // → "Amman" (the copy shares nothing)
public sealed class Customer
{
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public Address Address { get; set; } = new Address();
}
public sealed class Address
{
public string City { get; set; } = "";
}
Email: "a@x.com" -> "b@x.com"
Address.City: "Amman" -> "Irbid"
DeepClone copies through System.Text.Json, so nested objects and collections are copied too. CompareProperties compares nested objects property by property, reports them with a dotted path, and keeps declaration order. Each PropertyChange also exposes PropertyName, OldValue and NewValue.
DeepClone throws JsonException when the object graph has a cycle. Both methods use reflection, so they produce a trim warning in a trimmed or Native AOT app. See Trimming and AOT.
Learn more: Compare, clone and map.
Step 6: Time
Describe dates for people and do calendar arithmetic. No method reads the system clock: you pass "now" in, so the code stays testable.
DateTime now = new DateTime(2024, 5, 15, 12, 0, 0, DateTimeKind.Utc); // a Wednesday
string posted = now.AddMinutes(-5).ToRelativeTime(now); // → "5 minutes ago"
string updated = now.AddDays(-1).ToRelativeTime(now); // → "yesterday"
DateTime weekStart = now.StartOfWeek(); // → 2024-05-13 00:00 (Monday)
DateTime invoiceDate = new DateTime(2024, 5, 17, 9, 30, 0); // a Friday
DateTime dueDate = invoiceDate.AddBusinessDays(1); // → 2024-05-20 09:30 (Monday)
TimeSpan duration = 1.Days() + 2.Hours() + 30.Minutes();
string full = duration.ToHumanString(); // → "1 day, 2 hours, 30 minutes"
string brief = duration.ToHumanString(2); // → "1 day, 2 hours"
StartOfWeek uses Monday (ISO 8601) unless you pass another DayOfWeek. AddBusinessDays skips Saturdays and Sundays and keeps the time of day. It does not know public holidays. ToHumanString(2) keeps the two largest units and drops the rest without rounding. "1 day, 2 hours, 30 minutes".ToTimeSpan() reads the text back.
Give ToRelativeTime a reference date with the same DateTimeKind as the value. Mixing UTC and local times shifts the result by your time zone offset.
Step 7: Console output
Print data as a table or a small chart in a console app, a log or a test report. Printing is the point here, so this snippet calls Console.WriteLine.
OrderRow[] orders =
{
new OrderRow("Ahmad", 3, 249.90m),
new OrderRow("Lina", 12, 1200m),
};
Console.WriteLine(orders.ToTable());
int[] latenciesMs = { 120, 340, 90, 410, 260 };
Console.WriteLine(latenciesMs.ToSparkline());
(string, double)[] requests = { ("GET", 1240), ("POST", 410) };
Console.WriteLine(requests.ToBarChart(20));
public sealed record OrderRow(string Customer, int Items, decimal Total);
┌──────────┬───────┬────────┐
│ Customer │ Items │ Total │
├──────────┼───────┼────────┤
│ Ahmad │ 3 │ 249.90 │
│ Lina │ 12 │ 1200 │
└──────────┴───────┴────────┘
▂▆▁█▅
GET ████████████████████ 1,240
POST ██████▋ 410
ToTable makes one column per public property and right-aligns numbers. ToSparkline draws one block per value, scaled between the smallest and the largest value. ToBarChart scales every bar to the largest value. Its number argument is the width of the longest bar in characters, and it is 40 when you leave it out.
ToTable formats numbers with the current culture, so on a de-DE machine 249.90 prints as 249,90. If the box characters print as ? on Windows, set Console.OutputEncoding = System.Text.Encoding.UTF8; before you print.
Learn more: Console output.
Next steps
- Read Conventions for the null, culture and exception rules that every method follows.
- Browse Examples for longer, real-world scenarios.
- Use the API reference to find any method by name.