Programming foundations: C#

Sefism.

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 dotnet command from the .NET SDK creates, builds, runs and tests projects. Each project is described by a .csproj file; 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.
Shell
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.Tests

Watch 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#.
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), string means never null and string? 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.
C#
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 get and set accessors. init allows setting only during creation, and required forces 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.
C#
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 record is a type for data with value-based equality, a readable ToString and non-destructive changes through with.
Why
Use records for data that should be compared by value, such as money, settings or messages, instead of writing Equals by hand.
C#
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, GroupBy and OrderBy to 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.
C#
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 async method returns a Task and can await other 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.WhenAll runs independent calls at the same time.
C#
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.

03 · C# essentials3 / 10