C# Cheat Sheet
This cheat sheet is based on C# 14 and .NET 10. It covers everyday language features and standard library APIs with examples.
C# version comparison
Section titled “C# version comparison”The table lists selected additions through C# 14, the stable baseline used here. It is a feature history, not a support-lifecycle table. Preview features are excluded.
| C# | Released | Main additions |
|---|---|---|
| 1.0 / 1.2 | 2002 / 2003 | Classes, structs, interfaces, delegates; refinements in 1.2 |
| 2.0 | 2005 | Generics, nullable value types, iterators, anonymous methods |
| 3.0 | 2007 | LINQ, lambdas, extension methods, var, anonymous types |
| 4.0 | 2010 | dynamic, named and optional arguments, generic covariance and contravariance |
| 5.0 | 2012 | async / await, caller information |
| 6.0 | 2015 | String interpolation, ?., nameof, expression-bodied members |
| 7.0 | 2017 | Tuples and deconstruction, pattern matching, local functions, out var |
| 7.1 | 2017 | async Main, default literals |
| 7.2 | 2017 | readonly struct, ref struct, in parameters |
| 7.3 | 2018 | unmanaged, Enum, and Delegate constraints |
| 8.0 | 2019 | Nullable reference types, switch expressions, async streams, ranges, using declarations |
| 9.0 | 2020 | Records, init, top-level statements, target-typed new |
| 10 | 2021 | record struct, global using, file-scoped namespaces |
| 11 | 2022 | Raw strings, required, list patterns, generic math |
| 12 | 2023 | Collection expressions, primary constructors for classes and structs |
| 13 | 2024 | params collections, System.Threading.Lock support, relaxed ref struct restrictions |
| 14 | 2025 | Extension members, field, null-conditional assignment |
Official C# version history / Feature-by-feature history
Target framework and default language version
Section titled “Target framework and default language version”With a current SDK and no LangVersion override, the compiler selects a language version from the target framework. This mapping is separate from the original release environment of older C# versions.
| Target | Default C# |
|---|---|
| .NET 10 | 14 |
| .NET 9 | 13 |
| .NET 8 | 12 |
| .NET 7 | 11 |
| .NET 6 | 10 |
| .NET 5 | 9.0 |
| .NET Core 3.x / .NET Standard 2.1 | 8.0 |
| .NET Core 2.x / .NET Standard 2.0 / .NET Framework | 7.3 |
Prefer the target framework’s default. Setting LangVersion to latest makes the result depend on the installed compiler and does not supply missing runtime APIs. global.json selects the SDK; TargetFramework selects the target; LangVersion controls accepted syntax. Using a newer language version than the target’s supported mapping is unsupported.
Visual Studio version mapping
Section titled “Visual Studio version mapping”The table shows the Windows Visual Studio release that introduced each stable C# version. It is a historical introduction map, not a list of currently supported IDE releases. Visual Studio Code is a separate product.
| C# | Visual Studio at introduction |
|---|---|
| 1.0 | Visual Studio .NET 2002 |
| 1.2 | Visual Studio .NET 2003 |
| 2.0 | Visual Studio 2005 |
| 3.0 | Visual Studio 2008 |
| 4.0 | Visual Studio 2010 |
| 5.0 | Visual Studio 2012 |
| 6.0 | Visual Studio 2015 |
| 7.0 | Visual Studio 2017 (15.0) |
| 7.1 | Visual Studio 2017 (15.3) |
| 7.2 | Visual Studio 2017 (15.5) |
| 7.3 | Visual Studio 2017 (15.7) |
| 8.0 | Visual Studio 2019 (16.3) |
| 9.0 | Visual Studio 2019 (16.8) |
| 10 | Visual Studio 2022 (17.0) |
| 11 | Visual Studio 2022 (17.4) |
| 12 | Visual Studio 2022 (17.8) |
| 13 | Visual Studio 2022 (17.12) |
| 14 | Visual Studio 2026 (18.0) |
Later compiler versions also understand older C# syntax, but opening and building an old project still depends on its project type, target framework, and installed workloads. The year in a product name and its version number differ: Visual Studio 2022 is the 17.x family; Visual Studio 2026 is the 18.x family.
Official C# / Visual Studio history
Targeting recent .NET versions in Visual Studio
Section titled “Targeting recent .NET versions in Visual Studio”| Target | Default C# | Minimum Visual Studio for targeting |
|---|---|---|
| .NET 8 | 12 | Visual Studio 2022 (17.8) or later |
| .NET 9 | 13 | Visual Studio 2022 (17.12) or later |
| .NET 10 | 14 | Visual Studio 2026 (18.0) or later |
An SDK being usable by an older Visual Studio does not mean that IDE supports targeting the SDK’s newest runtime. For example, .NET 10 SDK 10.0.100 can work with Visual Studio 2022 17.14, but targeting net10.0 in Visual Studio requires 2026 18.0 or later. Also check the SDK feature band selected by global.json against the IDE’s supported SDKs. With dotnet build outside Visual Studio, the installed SDK and project settings determine the build environment.
Official SDK / MSBuild / Visual Studio compatibility
How common syntax changed
Section titled “How common syntax changed”| Task | Earlier form | Newer form | Introduced |
|---|---|---|---|
| Interpolation | "Hello, " + name |
$"Hello, {name}" |
6 |
| Assign a null fallback | if (name == null) name = "guest"; |
name ??= "guest"; |
8 |
| Construct an object | List<int> values = new List<int>(); |
List<int> values = new(); |
9 |
| Initialize an array | int[] values = new[] { 1, 2 }; |
int[] values = [1, 2]; |
12 |
| Assign through a non-null receiver | if (person is not null) person.Name = "Ada"; |
person?.Name = "Ada"; |
14 |
Older forms remain valid. The newer syntax may reduce repetition; it does not generally imply faster execution. In the snippets below, raw strings and required need C# 11, collection expressions need C# 12, and the System.Threading.Lock example uses C# 13 with .NET 9 or later.
Installation and project setup
Section titled “Installation and project setup”Install the .NET 10 SDK for your OS from the official .NET download page. The runtime alone cannot build applications. For an existing project, follow the SDK selection in global.json and the target framework in its .csproj file.
dotnet --versiondotnet --list-sdksdotnet new console -n HelloCSharp --framework net10.0cd HelloCSharpdotnet rundotnet build -c ReleaseProgram structure
Section titled “Program structure”Write this example in the console project’s Program.cs. Top-level statements let you omit an explicit Main method.
// Write to standard output.Console.WriteLine("Hello, C#!");Console.WriteLine($"Arguments: {args.Length}");Unless stated otherwise, each C# block on this page can replace Program.cs independently. Examples assume a net10.0 console project with ImplicitUsings and Nullable enabled. Type declarations follow top-level statements.
Comments
Section titled “Comments”Use // for line comments, /* ... */ for block comments, and /// for XML documentation comments.
Console.WriteLine(MathHelpers.Add(2, 3));
public static class MathHelpers{ /// <summary>Adds two integers.</summary> public static int Add(int left, int right) => left + right;}Variables, constants, and built-in types
Section titled “Variables, constants, and built-in types”var infers a static type from the initializer; it does not make a variable dynamically typed. Local variables must be assigned before use.
int count = 3;long population = 8_000_000_000L;double ratio = 0.5;decimal price = 1_980.50m;bool enabled = true;char initial = 'C';string language = "C#";var names = new List<string>();const int MaxRetries = 3;Console.WriteLine($"{count}, {population}, {ratio}, {price}, {enabled}, {initial}, {language}, {names.Count}, {MaxRetries}");| Type | Purpose and notes |
|---|---|
byte / sbyte |
Unsigned / signed 8-bit integers |
short / ushort |
16-bit integers |
int / uint |
32-bit integers |
long / ulong |
64-bit integers |
nint / nuint |
Platform-sized integers |
float / double |
Binary floating point; use f for float literals |
decimal |
Decimal arithmetic; use m for literals. Precision and range are still limited |
bool |
true / false |
char |
One UTF-16 code unit, not necessarily a complete character |
string |
An immutable UTF-16 string |
object |
The common base type; assigning a value type involves boxing |
Fields and array elements receive default values: 0 for numbers, false for bool, and null for reference types. A readonly field can be assigned at its declaration or in a constructor, unlike a compile-time const.
Conversions and numeric operations
Section titled “Conversions and numeric operations”Integer division produces an integer. TryParse reports conversion failure through its return value, making it useful for external input.
Console.WriteLine(5 / 2); // 2Console.WriteLine(5 / 2.0); // 2.5if (int.TryParse("42", out int value)) Console.WriteLine(value);
double source = 3.9;int truncated = (int)source;Console.WriteLine(truncated);try{ int max = int.MaxValue; Console.WriteLine(checked(max + 1));}catch (OverflowException){ Console.WriteLine("Overflow");}Use checked to detect overflow in integer arithmetic and conversions. Specify a culture when persisting or exchanging formatted numbers and dates.
Null and nullable reference types
Section titled “Null and nullable reference types”int? is a nullable value type. string? tells the compiler that a reference may be null. Nullable reference type warnings do not add runtime checks.
string? name = null;int? length = name?.Length;Console.WriteLine(length ?? 0);name ??= "guest";if (name is { Length: > 0 }) Console.WriteLine(name.ToUpperInvariant());
int? score = null;Console.WriteLine(score.GetValueOrDefault());?. skips access on null, ?? supplies a fallback, and ??= assigns only on null. The null-forgiving operator ! only suppresses warnings; it does not make a value safe.
Strings
Section titled “Strings”Strings are immutable. Use $"..." for interpolation, @"..." to preserve backslashes in paths, and raw string literals for multiline text.
string name = "Ada";string path = @"C:\work\notes.txt";string json = """ {"name":"Ada","active":true} """;Console.WriteLine($"Hello, {name}! {path}");Console.WriteLine(json);Console.WriteLine(string.Join(" | ", " alpha,beta ".Trim().Split(',')));Console.WriteLine(string.Join(" / ", new[] { "alpha", "beta" }));Console.WriteLine("CSharp".Contains("sharp", StringComparison.OrdinalIgnoreCase));Console.WriteLine(name[..2]);Specify StringComparison.Ordinal or OrdinalIgnoreCase for identifier-like comparisons. Consider culture for human-language text. StringBuilder is useful for repeated concatenation.
Control flow and pattern matching
Section titled “Control flow and pattern matching”Use if, for, foreach, while, and do for control flow. A switch expression selects a value using patterns.
int score = 85;if (score >= 60) Console.WriteLine("Pass");
string grade = score switch{ >= 90 => "A", >= 80 => "B", >= 60 => "C", _ => "D"};Console.WriteLine(grade);for (int i = 0; i < 3; i++) Console.WriteLine(i);foreach (int number in new[] { 1, 2, 3 }){ if (number == 2) continue; Console.WriteLine(number);}int remaining = 2;while (remaining > 0) remaining--;do { remaining++; } while (remaining < 1);
object item = "hello";if (item is string { Length: > 0 } text) Console.WriteLine(text);int[] values = [1, 2, 3];Console.WriteLine(values is [1, .., 3]);break exits a loop or switch statement; continue advances to the next iteration. Switch statement sections normally end with break or return and do not implicitly fall through.
Methods, parameters, and tuples
Section titled “Methods, parameters, and tuples”You can declare local functions. Use tuples for multiple results, params for a variable number of arguments, and name: value for named arguments.
Console.WriteLine(Greet(name: "Ada"));Console.WriteLine(Sum(1, 2, 3));var (min, max) = Bounds([8, 2, 5]);Console.WriteLine($"{min}..{max}");int count = 1;Increment(ref count);Console.WriteLine(count);
static string Greet(string name, string prefix = "Hello") => $"{prefix}, {name}";static int Sum(params int[] values) => values.Sum();static (int Min, int Max) Bounds(int[] values) => (values.Min(), values.Max());static void Increment(ref int value) => value++;ref passes an initialized variable by reference, out requires assignment by the callee, and in passes a readonly reference. Ordinary arguments pass by value; for reference types, that value is a reference.
Arrays, List, Dictionary, and HashSet
Section titled “Arrays, List, Dictionary, and HashSet”Arrays have a fixed length. List<T> is an array-backed collection that can grow. Dictionary<TKey, TValue> stores key-value pairs, while HashSet<T> stores unique values.
int[] numbers = [10, 20, 30];Console.WriteLine(numbers[^1]);int[] firstTwo = numbers[..2];List<string> names = ["Ada", "Linus"];names.Add("Grace");names.Remove("Linus");Console.WriteLine($"{numbers.Length}, {firstTwo.Length}, {names.Count}");
var scores = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase){ ["Ada"] = 90};if (scores.TryGetValue("ada", out int score)) Console.WriteLine(score);scores["Grace"] = 95;foreach (var (name, points) in scores) Console.WriteLine($"{name}: {points}");
HashSet<int> unique = [1, 1, 2];Console.WriteLine(unique.Add(2));Console.WriteLine(unique.Contains(1));An array range such as numbers[..2] creates a new array. A list’s Capacity is its allocated capacity; Count is its actual element count. Ordinary collections require synchronization for concurrent mutation.
Classes, properties, and accessibility
Section titled “Classes, properties, and accessibility”Classes are reference types. required requires initialization, and init limits property assignment to initialization.
var account = new Account("A-001") { Owner = "Ada" };account.Deposit(100m);Console.WriteLine($"{account.Id}: {account.Owner}, {account.Balance}");
public sealed class Account(string id){ public string Id { get; } = id; public required string Owner { get; init; } public decimal Balance { get; private set; }
public void Deposit(decimal amount) { if (amount <= 0) throw new ArgumentOutOfRangeException(nameof(amount)); Balance += amount; }}| Modifier | Accessibility |
|---|---|
public |
Unrestricted, within the containing type’s accessibility |
private |
Within the declaring type |
protected |
Within the declaring type and derived types |
internal |
Within the same assembly |
protected internal |
Same assembly or derived types |
private protected |
Declaring type and derived types in the same assembly |
sealed prevents inheritance. Primary constructor parameters do not automatically become public properties on ordinary classes. required does not validate the supplied value.
Structs, records, and equality
Section titled “Structs, records, and equality”A struct is a value type: assignment copies its value. Records generate value-based equality. A plain record is a reference type; a record struct is a value type.
var original = new Person("Ada", 36);var updated = original with { Age = 37 };Console.WriteLine(original == new Person("Ada", 36));Console.WriteLine(updated);var point = new Point(1, 2);var copy = point with { X = 5 };Console.WriteLine($"{point} / {copy}");
public record Person(string Name, int Age);public readonly record struct Point(int X, int Y);with makes a shallow copy. Reference-type members remain shared, so records do not guarantee deep immutability. Value types can also contain reference-type fields whose referenced objects remain shared after copying.
Inheritance and interfaces
Section titled “Inheritance and interfaces”A class can inherit one base class and implement multiple interfaces. Interfaces express shared contracts.
IShape shape = new Circle(3);Console.WriteLine(shape.Area);Animal animal = new Dog();animal.Speak();
public interface IShape{ double Area { get; }}public sealed class Circle(double radius) : IShape{ public double Area => Math.PI * radius * radius;}public abstract class Animal{ public abstract void Speak();}public sealed class Dog : Animal{ public override void Speak() => Console.WriteLine("Woof");}Use virtual for an overridable method with a base implementation and override to override it. This differs from hiding a member with new.
Enums and flags
Section titled “Enums and flags”Enums represent named integer values. For flags, assign values to individual bits.
Access access = Access.Read | Access.Write;Console.WriteLine(access.HasFlag(Access.Read));Console.WriteLine((access & Access.Write) != 0);Console.WriteLine(Enum.IsDefined(Status.Ready));
public enum Status { Unknown, Ready, Done }[Flags]public enum Access { None = 0, Read = 1, Write = 2, Execute = 4 }Casting an integer can create an undefined enum value. Validate external input. Enum.IsDefined does not accept a flag combination unless that exact value is declared.
Generics
Section titled “Generics”Type parameters let you reuse algorithms and data structures while retaining type safety. where constrains the supported types.
Console.WriteLine(Max(3, 7));var box = new Box<string>("hello");Console.WriteLine(box.Value);
static T Max<T>(T left, T right) where T : IComparable<T> => left.CompareTo(right) >= 0 ? left : right;
public sealed class Box<T>(T value){ public T Value { get; } = value;}Common constraints include class, struct, notnull, interfaces, and new(). The new() constraint requires a public parameterless constructor and follows other ordinary constraints.
Lambdas, delegates, and events
Section titled “Lambdas, delegates, and events”Func<...> represents a function with a return value; Action<...> represents one without. Lambdas can capture outer variables. Events notify subscribers from a publisher.
int factor = 2;Func<int, int> multiply = value => value * factor;factor = 3;Console.WriteLine(multiply(4));Action<string> print = message => Console.WriteLine(message);print("Ready");
var counter = new Counter();EventHandler handler = (_, _) => Console.WriteLine("Changed");counter.Changed += handler;counter.Increment();counter.Changed -= handler;
public sealed class Counter{ public int Value { get; private set; } public event EventHandler? Changed; public void Increment() { Value++; Changed?.Invoke(this, EventArgs.Empty); }}A capture retains the variable, not a snapshot of its value. Unsubscribe when a subscription to a long-lived publisher is no longer needed. Use static on a lambda that should not capture outer state.
LINQ expresses filtering, projection, and aggregation over data sources. It supports method syntax and query syntax.
int[] numbers = [1, 2, 3, 4, 5];var query = numbers.Where(n => n % 2 == 0).Select(n => n * 10);int[] result = query.ToArray();Console.WriteLine(string.Join(", ", result));Console.WriteLine(numbers.Any(n => n > 4));Console.WriteLine(numbers.All(n => n > 0));Console.WriteLine(numbers.Sum());Console.WriteLine(numbers.FirstOrDefault(n => n > 10, -1));
var descending = from n in numbers where n >= 3 orderby n descending select n;Console.WriteLine(string.Join(", ", descending));foreach (var group in numbers.GroupBy(n => n % 2)) Console.WriteLine($"{group.Key}: {group.Count()}");| Operation | Common methods |
|---|---|
| Filter and project | Where, Select, SelectMany |
| Sort | OrderBy, OrderByDescending, ThenBy |
| First, last, single | First, Last, Single and their OrDefault variants |
| Limit results | Take, Skip, TakeWhile |
| Aggregate | Count, Sum, Average, Min, Max, Aggregate |
| Set operations | Distinct, Union, Intersect, Except |
| Materialize | ToArray, ToList, ToDictionary |
Where and Select usually run when enumerated (deferred execution). Enumerating again repeats the work; materialize when you need to retain the results. SingleOrDefault still throws for multiple matches. ToDictionary throws on duplicate keys. An IQueryable<T> provider may translate a query to SQL or another language, so its execution rules differ from in-memory LINQ.
Iterators and yield
Section titled “Iterators and yield”Use yield return to produce an iterator’s elements one at a time without first storing them in an array.
foreach (int value in CountUp(3)) Console.WriteLine(value);
static IEnumerable<int> CountUp(int count){ for (int i = 0; i < count; i++) yield return i;}Iterator bodies normally execute, and may throw, during enumeration rather than at the initial method call. yield break ends enumeration.
Exception handling
Section titled “Exception handling”Catch specific exceptions you can handle. Use finally or using to release resources.
try{ Console.WriteLine(ParsePositive("-1"));}catch (ArgumentOutOfRangeException ex){ Console.WriteLine(ex.ParamName);}catch (FormatException){ Console.WriteLine("Invalid number");}finally{ Console.WriteLine("Finished");}
static int ParsePositive(string text){ int value = int.Parse(text); if (value <= 0) throw new ArgumentOutOfRangeException(nameof(text), "Must be positive."); return value;}Use throw; to rethrow while preserving the original stack trace. throw ex; resets it. For expected input failures, consider APIs such as TryParse instead of using exceptions for routine branching.
Using and resource management
Section titled “Using and resource management”The GC manages memory, but resources such as files and connections need timely cleanup. using calls IDisposable.Dispose when its scope ends.
using var stream = new MemoryStream();using (var writer = new StreamWriter(stream, leaveOpen: true)){ writer.Write("hello");}stream.Position = 0;using var reader = new StreamReader(stream);Console.WriteLine(reader.ReadToEnd());Use await using for asynchronous cleanup through IAsyncDisposable. This differs from a using directive that imports a namespace.
Async, await, and cancellation
Section titled “Async, await, and cancellation”Async methods commonly return Task or Task<T>. When an operation is incomplete, await can yield control to the caller. The async keyword does not itself start a new thread.
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));try{ Task<int> first = WorkAsync(1, cts.Token); Task<int> second = WorkAsync(2, cts.Token); int[] results = await Task.WhenAll(first, second); Console.WriteLine(string.Join(", ", results));}catch (OperationCanceledException) when (cts.IsCancellationRequested){ Console.WriteLine("Canceled");}
static async Task<int> WorkAsync(int value, CancellationToken cancellationToken){ await Task.Delay(20, cancellationToken); return value * 2;}Cancellation is cooperative. Pass the token to downstream APIs and call ThrowIfCancellationRequested() in CPU work where appropriate. Task.WhenAll waits for every task; a failure does not automatically cancel the others.
Avoid blocking with .Result or .Wait() and propagate await through callers. Usually reserve async void for event handlers. Use asynchronous APIs for I/O; consider Task.Run when CPU-intensive work needs to run on another thread.
Official guide — asynchronous programming
Async streams
Section titled “Async streams”IAsyncEnumerable<T> produces elements asynchronously. Enumerate it with await foreach.
using System.Runtime.CompilerServices;
using var cts = new CancellationTokenSource();await foreach (int value in ReadAsync(cts.Token)) Console.WriteLine(value);
static async IAsyncEnumerable<int> ReadAsync( [EnumeratorCancellation] CancellationToken cancellationToken = default){ for (int i = 0; i < 3; i++) { await Task.Delay(10, cancellationToken); yield return i; }}Concurrency and synchronization
Section titled “Concurrency and synchronization”Synchronize updates to shared state. Use Interlocked for simple counters and lock to protect a group of operations.
int count = 0;Parallel.For(0, 1_000, _ => Interlocked.Increment(ref count));Console.WriteLine(count);
var gate = new System.Threading.Lock();List<int> values = [];Parallel.For(0, 10, i =>{ lock (gate) { values.Add(i); }});Console.WriteLine(values.Count);You cannot await inside a lock. For asynchronous concurrency limits, use SemaphoreSlim.WaitAsync and release in finally. Concurrent collections do not automatically make a sequence of multiple operations atomic.
Span and Memory
Section titled “Span and Memory”Span<T> is a view over contiguous memory; slicing it does not copy elements. ReadOnlySpan<T> prevents writes through that view.
int[] values = [1, 2, 3, 4];Span<int> middle = values.AsSpan(1, 2);middle[0] = 20;Console.WriteLine(values[1]);ReadOnlySpan<char> prefix = "hello".AsSpan(0, 2);Console.WriteLine(prefix.ToString());Span<T> is a ref struct: it cannot be stored in an ordinary class field or used across an await boundary. Consider Memory<T> for those lifetimes. A readonly view does not prevent other code from modifying the underlying array.
Standard library quick reference
Section titled “Standard library quick reference”Frequently used types and namespaces.
| Purpose | Type or namespace |
|---|---|
| Build strings | System.Text.StringBuilder |
| Regular expressions | System.Text.RegularExpressions.Regex |
| Dates and durations | DateTimeOffset, DateOnly, TimeOnly, TimeSpan |
| Files and paths | System.IO.File, Directory, Path |
| JSON | System.Text.Json.JsonSerializer |
| HTTP | System.Net.Http.HttpClient |
| UUIDs | Guid |
| Non-cryptographic randomness | Random.Shared |
| Cryptographic randomness | System.Security.Cryptography.RandomNumberGenerator |
| Concurrent collections | System.Collections.Concurrent |
| Async queues | System.Threading.Channels |
| Elapsed time | System.Diagnostics.Stopwatch |
Dates and durations
Section titled “Dates and durations”Use UTC or an explicit offset for stored or transmitted timestamps, then convert for display.
using System.Globalization;
DateTimeOffset now = DateTimeOffset.UtcNow;string encoded = now.ToString("O", CultureInfo.InvariantCulture);DateTimeOffset restored = DateTimeOffset.Parse(encoded, CultureInfo.InvariantCulture);DateOnly date = new(2026, 9, 14);TimeOnly time = new(9, 30);TimeSpan duration = TimeSpan.FromMinutes(45);Console.WriteLine($"{restored:O}, {date:yyyy-MM-dd}, {time:HH:mm}, {duration}");DateTimeOffset stores an offset, not a time zone’s rules. Use TimeZoneInfo for conversions involving daylight saving time and other zone rules.
Files and paths
Section titled “Files and paths”This example creates a temporary file, reads and writes it, then deletes it.
string path = Path.GetTempFileName();try{ await File.WriteAllTextAsync(path, "hello"); string text = await File.ReadAllTextAsync(path); Console.WriteLine(text); Console.WriteLine(Path.GetFileName(path));}finally{ File.Delete(path);}Process large files incrementally with streams or File.ReadLines. Path.Combine joins paths; it does not validate that external input stays inside an allowed directory.
Use the built-in System.Text.Json APIs to serialize and deserialize data.
using System.Text.Json;
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };var person = new Person("Ada", 36);string json = JsonSerializer.Serialize(person, options);Console.WriteLine(json);Person restored = JsonSerializer.Deserialize<Person>(json, options) ?? throw new JsonException("Expected a person.");Console.WriteLine(restored.Name);
public record Person(string Name, int Age);Ordinary defaults match property names case-sensitively; web defaults differ. Validate business requirements after deserialization as well.
This example requires network access. Reuse HttpClient for an appropriate application lifetime, or manage it with IHttpClientFactory in a DI-based application.
using System.Net;
using var handler = new SocketsHttpHandler{ PooledConnectionLifetime = TimeSpan.FromMinutes(2), AutomaticDecompression = DecompressionMethods.All};using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));using var response = await client.GetAsync("https://example.com/", cts.Token);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync(cts.Token));This console example disposes the client on exit. Avoid creating and disposing a client per request in a long-running application. Handle HTTP failures, connection errors, timeouts, and cancellation according to your application’s requirements.
Official guide — HttpClient guidelines
Namespaces, projects, and NuGet
Section titled “Namespaces, projects, and NuGet”Namespaces organize type names. The following is a file in a class library, not an executable Program.cs example.
namespace Sample.Library;
public static class Calculator{ public static int Add(int left, int right) => left + right;}A file-scoped namespace applies to the whole file. using Sample.Library; shortens type names, but using another project also requires a project reference.
CLI commands
Section titled “CLI commands”These commands create a separate solution for the examples. In .NET 10, dotnet new sln creates a .slnx file by default.
dotnet new sln -n CheatSheetdotnet new classlib -n Sample.Library --framework net10.0dotnet new console -n Sample.App --framework net10.0dotnet sln CheatSheet.slnx add Sample.Library/Sample.Library.csproj Sample.App/Sample.App.csprojdotnet reference add Sample.Library/Sample.Library.csproj --project Sample.App/Sample.App.csprojdotnet restore CheatSheet.slnxdotnet build CheatSheet.slnxdotnet format CheatSheet.slnx --verify-no-changesdotnet publish Sample.App/Sample.App.csproj -c ReleaseAdd a NuGet package with dotnet package add PACKAGE --project PROJECT_PATH. Check its official NuGet page for stable releases and supported frameworks before adopting it. Use dotnet package list --outdated to check for updates. Pin package and SDK versions when reproducibility matters.
Testing
Section titled “Testing”Add an MSTest project to the solution above. The template configures the test packages. This example explicitly selects MSTest 4.4.0.
dotnet new mstest -n Sample.Tests --framework net10.0dotnet package add MSTest --version 4.4.0 --project Sample.Tests/Sample.Tests.csprojdotnet sln CheatSheet.slnx add Sample.Tests/Sample.Tests.csprojdotnet reference add Sample.Library/Sample.Library.csproj --project Sample.Tests/Sample.Tests.csprojdotnet test CheatSheet.slnxTest code
Section titled “Test code”Place the Calculator shown above in Sample.Library, then replace the generated test file in the test project with this code.
using Microsoft.VisualStudio.TestTools.UnitTesting;using Sample.Library;
namespace Sample.Tests;
[TestClass]public sealed class CalculatorTests{ [TestMethod] [DataRow(2, 3, 5)] [DataRow(-2, 2, 0)] [DataRow(0, 0, 0)] public void Add_ReturnsSum(int left, int right, int expected) { Assert.AreEqual(expected, Calculator.Add(left, right)); }}This block belongs in the MSTest project and cannot run as a standalone console application. Run dotnet test CheatSheet.slnx again after the changes. Async tests should return async Task and await the operation.
Official guide — C# and MSTest
C# 14 syntax
Section titled “C# 14 syntax”C# 14 adds extension members, field-backed properties, and null-conditional assignment, among other features. Here are representative examples.
Console.WriteLine("hello".IsLong);var person = new Person();person.Name = " Ada ";Console.WriteLine(person.Name);Person? missing = null;missing?.Name = "Grace";Console.WriteLine(nameof(List<>));
public static class TextExtensions{ extension(string text) { public bool IsLong => text.Length >= 5; }}public sealed class Person{ public string Name { get; set => field = value.Trim(); } = "";}Null-conditional assignment skips evaluation of the right-hand side when the receiver is null. field refers to a compiler-generated backing field. The existing this extension-method syntax remains supported.
Official reference — what’s new in C# 14
Tips and common mistakes
Section titled “Tips and common mistakes”- Losing fractions in integer division: convert at least one operand to a floating-point type or
decimalbefore dividing. - Assuming
==always means the same thing: ordinary classes default to reference equality, records use value equality, and strings compare content. UseSequenceEqualto compare array elements. - Relying on nullable
!: it does not check for null at runtime. Validate input. - Enumerating LINQ repeatedly: watch for deferred execution and retain results with
ToListor similar when needed. - Modifying a
List<T>duringforeach: useRemoveAllor project into another collection. - Forgetting to await: observe task completion and exceptions with
await. - Treating
asyncas parallelism: distinguish asynchronous I/O from parallel CPU work. - Generating secrets with
Random: useRandomNumberGeneratorfor cryptographic purposes. - Storing
DateTime.Nowwithout time zone context: use UTC or an explicit offset. - Assuming records or
readonlyguarantee deep immutability: referenced objects can still be mutable.