Chapter 03
C# essentials
The C# and .NET features you meet from day one: the dotnet CLI, value and reference types, nullable references, properties, records, LINQ and async code.
The dotnet CLI
Also called .NET SDK, dotnet new, dotnet run, csproj, dotnet CLI
- What
- The
dotnetcommand from the .NET SDK creates, builds, runs and tests projects. Each project is described by a.csprojfile; a solution groups several projects. - Why
- Knowing the CLI means you understand what Visual Studio or Rider does for you, and you can build in CI or on any machine.
dotnet new console -o Ledger
dotnet new xunit -o Ledger.Tests
dotnet add Ledger.Tests reference Ledger/Ledger.csproj
dotnet run --project Ledger
dotnet test Ledger.TestsWatch out
Targeting an out-of-support .NET version from an old tutorial; use the current long-term support release.
Value types and reference types
Also called struct vs class, boxing
- What
- Value types (
int,bool,struct) are copied on assignment. Reference types (class,string, arrays,List<T>) are shared: assignment copies a reference to the same object. - Why
- It decides whether changing a copy changes the original, which explains most surprising mutations in C#.
var s1 = new PointS { X = 1 };
var s2 = s1;
s2.X = 9;
var c1 = new PointC { X = 1 };
var c2 = c1;
c2.X = 9;
Console.WriteLine($"{s1.X} {c1.X}"); // 1 9
struct PointS { public int X; }
class PointC { public int X; }Watch out
Making a large or mutable struct: every assignment copies it and changes to a copy are lost.
Nullable reference types
Also called string?, null-forgiving operator, NRT
- What
- With nullable reference types on (the default in new projects),
stringmeans never null andstring?means may be null. The compiler warns when you might dereference a null. - Why
- It moves the most common .NET crash,
NullReferenceException, from runtime to compile-time warnings.
string name = "Ada";
string? nickname = null;
int length = nickname?.Length ?? 0;
if (nickname is not null)
{
Console.WriteLine(nickname.ToUpper());
}Watch out
Silencing warnings with the ! operator instead of handling the null case.
Properties
Also called get, set, init, auto-properties
- What
- Properties look like fields to callers but run
getandsetaccessors.initallows setting only during creation, andrequiredforces callers to set it. - Why
- They let a class expose data while controlling changes, such as a balance that only its own methods can modify.
public class Product
{
public required string Name { get; init; }
public decimal Price { get; private set; }
public bool IsFree => Price == 0;
public void Discount(decimal percent) =>
Price -= Price * percent / 100;
}Watch out
Making every property public get; set; by habit, which lets any code put the object into an invalid state.
Records
Also called record class, with expression
- What
- A
recordis a type for data with value-based equality, a readableToStringand non-destructive changes throughwith. - Why
- Use records for data that should be compared by value, such as money, settings or messages, instead of writing
Equalsby hand.
var a = new Money(10m, "GBP");
var b = a with { Amount = 12m };
Console.WriteLine(a == new Money(10m, "GBP")); // True
Console.WriteLine(b); // Money { Amount = 12, Currency = GBP }
public record Money(decimal Amount, string Currency);Watch out
Expecting a record to be deeply immutable when it holds a List<T>: the list itself can still change.
LINQ
Also called Language Integrated Query, Where, Select
- What
- LINQ adds query methods such as
Where,Select,GroupByandOrderByto any collection, with lambdas for the conditions. - Why
- It turns filter, group and sort logic into one readable pipeline, and the same style queries databases through Entity Framework.
var topCustomers = orders
.Where(o => o.Total > 10)
.GroupBy(o => o.Customer)
.Select(g => (Customer: g.Key, Sum: g.Sum(o => o.Total)))
.OrderByDescending(x => x.Sum)
.Take(3)
.ToList();Watch out
Forgetting that LINQ is lazy: the query runs each time you enumerate it, so call ToList once if you reuse the result.
async and await
Also called Task, Task.WhenAll, asynchronous
- What
- An
asyncmethod returns aTaskand canawaitother tasks. While it waits for I/O, the thread is free to do other work. - Why
- Web requests, database calls and file I/O in .NET are asynchronous.
Task.WhenAllruns independent calls at the same time.
static async Task<string> LoadAsync(HttpClient http, string url)
{
string body = await http.GetStringAsync(url);
return body.Trim();
}
var tasks = urls.Select(url => LoadAsync(http, url));
string[] pages = await Task.WhenAll(tasks);Watch out
Blocking on a task with .Result or .Wait(), which wastes threads and can deadlock; await it instead.