Released · improving
C# guide · 6/6
Three features give C# much of its character: delegates and lambdas for passing functions as values, LINQ for declarative queries, and async/await for waiting on I/O without tying up a thread. Each builds on the one before it.
A delegate is a type-safe reference to a method. You can declare your own delegate types, but most code uses the built-in Func<...> (returns a value) and Action<...> (returns nothing). A lambda expression, (parameters) => expression, creates an anonymous method on the spot.
Func<int, int, int> add = (a, b) => a + b;
Func<string, bool> isLong = s => s.Length > 5;
Action<string> log = message => Console.WriteLine($"[log] {message}");
Console.WriteLine(add(2, 3)); // 5
Console.WriteLine(isLong("TASTEDEV")); // True
log("starting up");
// a method that takes another function as an argument
static List<int> Apply(List<int> items, Func<int, int> transform)
{
var result = new List<int>();
foreach (var item in items)
result.Add(transform(item));
return result;
}
var doubled = Apply([1, 2, 3], x => x * 2); // [2, 4, 6]
// lambdas can capture variables from the surrounding scope
int threshold = 10;
Func<int, bool> overThreshold = x => x > threshold;Events build on delegates too: button.Click += (sender, e) => ... attaches a lambda to an event.
LINQ (Language Integrated Query) gives you one consistent way to query arrays, lists, databases, and other data sources. The most common style is method syntax: chaining extension methods from System.Linq and passing lambdas to them.
List<Order> orders =
[
new("Ana", "Books", 18.00m),
new("Ben", "Electronics", 320.00m),
new("Ana", "Electronics", 45.00m),
new("Cai", "Books", 22.00m),
];
var bigOrders = orders
.Where(o => o.Amount >= 20m)
.OrderByDescending(o => o.Amount)
.Select(o => $"{o.Customer}: {o.Amount:F2}")
.ToList();
var byCategory = orders
.GroupBy(o => o.Category)
.Select(g => new { Category = g.Key, Total = g.Sum(o => o.Amount), Count = g.Count() });
foreach (var row in byCategory)
Console.WriteLine($"{row.Category}: {row.Count} orders, {row.Total:F2} total");
bool anyExpensive = orders.Any(o => o.Amount > 100m);
var first = orders.FirstOrDefault(o => o.Customer == "Cai");
record Order(string Customer, string Category, decimal Amount);(With top-level statements, type declarations go last.) Query syntax reads more like SQL and compiles to the same method calls:
var query =
from o in orders
where o.Category == "Books"
orderby o.Amount
select o.Customer;LINQ uses deferred execution: Where and Select only describe a query, which runs when you enumerate it or call ToList, Count, or First. Enumerating twice runs it twice, so call ToList() once if you will reuse the results.
File, network, and database operations spend most of their time waiting. An async method hands its thread back at each await while the operation is in flight, then resumes on the next line once it completes. Async methods return Task when they produce no value and Task<T> when they do.
using var http = new HttpClient();
static async Task<int> GetLengthAsync(HttpClient client, string url, CancellationToken token)
{
string body = await client.GetStringAsync(url, token);
return body.Length;
}
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
// await one operation at a time
int length = await GetLengthAsync(http, "https://learn.microsoft.com", cts.Token);
Console.WriteLine($"Length: {length}");
// start several operations at once and wait for all of them
string[] urls = ["https://dotnet.microsoft.com", "https://www.nuget.org"];
int[] lengths = await Task.WhenAll(urls.Select(u => GetLengthAsync(http, u, cts.Token)));
Console.WriteLine(string.Join(", ", lengths));
}
catch (OperationCanceledException)
{
Console.WriteLine("Timed out, so the requests were cancelled.");
}Top-level statements can use await directly. A handful of rules will keep you out of most async trouble:
Async suffix and have them return Task or Task<T>.async void for event handlers, because callers cannot catch its exceptions..Result or .Wait(); await all the way up. Blocking is a classic cause of deadlocks.CancellationToken in long-running operations so they can be stopped.Task.WhenAll.Reuse HttpClient instead of creating one per request; web apps usually get clients from IHttpClientFactory.
Func and Action delegates hold lambdas, letting you pass behavior as a value; lambdas can capture surrounding variables.Where, Select, GroupBy, and OrderBy to work with data declaratively, and it runs only when results are needed.async/await with Task keeps threads free while waiting, and CancellationToken makes long operations cancellable.dotnet CLI.async/await model and task-based patterns.
0 comments
Sign in · Sign in to leave a comment.
Be the first to comment.