Begin with cornerstone guides that help teams move from familiar C# into the latest, secure, and modern language patterns.
Async streams pair IAsyncEnumerable<T> with await foreach so asynchronous producers can yield values progressively instead of buffering everything first.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public sealed class PriceService
{
public async IAsyncEnumerable<decimal> ReadPricesAsync()
{
foreach (var price in new[] { 9.99m, 11.25m, 10.5m })
{
await Task.Delay(20);
yield return price;
}
}
}
public static class Program
{
public static async Task Main()
{
var service = new PriceService();
await foreach (decimal price in service.ReadPricesAsync())
{
Console.WriteLine($"Streamed price: {price:F2}");
}
}
}
With expressions let you copy immutable data and update only what changed, keeping your intent crisp and your models predictable.
using System;
public record DeploymentConfig(string Service, string Region, int Replicas, bool DryRun);
public static class Program
{
public static void Main()
{
var baseline = new DeploymentConfig("orders-api", "eastus", 2, DryRun: true);
var productionPlan = baseline with
{
Replicas = 4,
DryRun = false
};
Console.WriteLine($"Baseline: {baseline}");
Console.WriteLine($"Production: {productionPlan}");
}
}
nameof and CallerArgumentExpression help your APIs report exactly what went wrong, with refactor-safe names and call-site expressions.
using System;
using System.Runtime.CompilerServices;
public static class Guard
{
public static void That(
bool condition,
string message,
[CallerArgumentExpression("condition")] string? conditionExpression = null)
{
if (!condition)
{
throw new ArgumentException(
$"{message} (Condition: {conditionExpression})",
nameof(condition));
}
}
}
public static class Program
{
public static void Main()
{
var quantity = -2;
try
{
Guard.That(quantity > 0, "Quantity must be positive");
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine($"Param: {ex.ParamName}");
}
}
}
Start with readable query pipelines when you want data-heavy code to stay direct and expressive.
var people = new[]
{
new { Age = 30, LastName = "Smith", FullName = "Alice Smith" },
new { Age = 16, LastName = "Jones", FullName = "Bob Jones" },
new { Age = 25, LastName = "Brown", FullName = "Carol Brown" },
};
var adults = people
.Where(person => person.Age >= 18)
.OrderBy(person => person.LastName)
.Select(person => person.FullName);
Learn the async workflow most teams reach for first when modern apps need responsive I/O without callback sprawl.
public class ProfileService
{
public async Task<string> LoadProfileAsync(HttpClient client, string id)
{
var response = await client.GetAsync($"/profiles/{id}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
Use compiler-backed null-safety to make modern codebases more secure, intentional, and easier to reason about.
string? FindDisplayName() => null;
string? displayName = FindDisplayName();
if (displayName is null)
{
return;
}
Console.WriteLine(displayName.Length);