Every feature from C# 7.0 to C# 14, newest first. C# 14 gets a full section per feature. Older versions get one bullet per feature, a one-line purpose, a tiny sample, and a link to the page that goes deeper.
Interviewers like “what is new in C#” questions. They test whether you keep up and whether you know why a feature exists. Each sample below is compiled and tested on .NET 10 unless it is marked as a fragment. Fragments are features that need a whole project to show, such as top-level statements.
Each C# version is the default for one target framework. You can raise it with <LangVersion> in the project file. Features that need runtime support, such as default interface methods or the Lock type, still need the newer runtime.
Reading the figure. Grey dots are the 7.x point releases. Blue dots pair one C# version with one .NET release. The green dot is C# 14, the current version with .NET 10. Even .NET numbers (6, 8, 10) are long-term support releases.
C# 14 has eight features. The biggest is extension members. The rest remove small daily friction.
An extension block inside a static class names a receiver once. Inside it you can declare extension properties, methods, and static members that look like they belong to the extended type. Old this-parameter extension methods still work, and the two styles mix freely.
Before C# 14 you could only add instance methods. You could not add a property such as text.WordCount or a static factory such as List<int>.Repeat(...). Grouping members by receiver also removes repeated this parameters.
public static class V14TextExtensions
{
// Instance extension block: s is the receiver, named once for every member.
extension(string s)
{
/// <summary>Number of space-separated words.</summary>
/// <example><c>"a b c".WordCount</c> returns 3.</example>
public int WordCount => s.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
/// <summary>Upper-cases the text and adds "!".</summary>
/// <returns>The shouted text.</returns>
/// <example><c>"hi".Shout()</c> returns "HI!".</example>
public string Shout() => s.ToUpperInvariant() + "!";
}
// Static extension block: no receiver name, members are called on the type.
extension(string)
{
/// <summary>A line of dashes.</summary>
/// <param name="n">How many dashes.</param>
/// <returns>The dashes.</returns>
/// <example><c>string.Dashes(3)</c> returns "---".</example>
public static string Dashes(int n) => new('-', n);
}
// Generic receiver: works for every IEnumerable<T>.
extension<T>(IEnumerable<T> source)
{
/// <summary>true when the sequence has no items.</summary>
/// <example><c>new int[0].IsEmpty</c> returns true.</example>
public bool IsEmpty => !source.Any();
}
// Static members on a generic type.
extension<T>(List<T>)
{
/// <summary>A list holding count copies of item.</summary>
/// <param name="item">The value to repeat.</param>
/// <param name="count">How many copies.</param>
/// <returns>The new list.</returns>
/// <example><c>List<int>.Repeat(7, 2)</c> returns [7, 7].</example>
public static List<T> Repeat(T item, int count) => [.. Enumerable.Repeat(item, count)];
}
}
public static class V14ExtensionDemo
{
/// <summary>Uses every kind of extension member once.</summary>
/// <returns>The results joined by spaces.</returns>
/// <example><c>Demo()</c> returns "3 HI! --- True 2".</example>
public static string Demo()
{
int words = "a b c".WordCount; // instance extension property
string loud = "hi".Shout(); // instance extension method
string line = string.Dashes(3); // static extension method, 3 dashes
bool empty = Array.Empty<int>().IsEmpty; // generic extension property
int copies = List<int>.Repeat(7, 2).Count; // static member on List<T>, 2 copies
return $"{words} {loud} {line} {empty} {copies}";
}
}
Reading the figure. Blue boxes are the call sites you write. Green boxes are what runs. An instance member passes the receiver as the first argument. A static member passes nothing extra.
extension<T> block instead.ConditionalWeakTable.Inside a property accessor, field refers to the compiler-made backing field. You can write logic in one accessor and keep the other one automatic.
Before, adding one line of validation to an auto-property forced you to declare a private field by hand. Now the property stays a single declaration.
public sealed class V14Person
{
// set trims, get stays automatic. field is the hidden backing store.
public string Name { get; set => field = value.Trim(); } = "";
// Validation without a hand-written _age field.
public int Age
{
get;
set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));
}
// Lazy default: computed on first read, then stored.
public string Display => field ??= $"{Name} ({Age})";
/// <summary>Sets a padded name and an age, then reads them back.</summary>
/// <returns>The display text.</returns>
/// <example><c>V14Person.Demo()</c> returns "Ann (30)".</example>
public static string Demo()
{
var p = new V14Person { Name = " Ann ", Age = 30 }; // 30: sample age
return p.Display;
}
}
field, the keyword wins inside accessors. Use this.field or @field for the old member.= "" set the backing field directly. They skip the set logic.field change the compiled output compared to a hand-written private field??. and ?[] can now appear on the left of = and of compound assignments such as +=. If the receiver is null, nothing happens, and the right side is not evaluated.
It replaces the if (x is not null) x.Prop = value; pattern with one line.
public sealed class V14Order
{
public string Status = "new";
public int Items;
public int[] Slots = [0, 0];
}
public static class V14NullAssign
{
/// <summary>Assigns through ?. on a null and a non-null order.</summary>
/// <returns>Status, items, slot 1, and how often the right side ran.</returns>
/// <example><c>Demo()</c> returns "paid 3 9 calls=3".</example>
public static string Demo()
{
int calls = 0;
string Paid() { calls++; return "paid"; }
V14Order? missing = null;
missing?.Status = Paid(); // receiver null: Paid() never runs
V14Order? order = new();
order?.Status = Paid(); // runs: calls becomes 1
order?.Items += 3; // compound works. 3: sample count
order?.Slots[1] = 9; // index 1: second slot. 9: sample
Paid(); Paid(); // two direct calls, so calls = 3
return $"{order!.Status} {order.Items} {order.Slots[1]} calls={calls}";
}
}
++ and -- are not allowed with ?.. Write x?.Count += 1.a?.B = F(), when does F run?a is not null. The whole assignment short-circuits.nameof(List<>) and nameof(Dictionary<,>) now compile. Before, you had to pick a dummy type argument such as List<int>.
Logging, error messages and analyzers name generic types without caring about the arguments.
public static class V14NameOf
{
/// <summary>Names generic types and a member without type arguments.</summary>
/// <returns>The names joined by spaces.</returns>
/// <example><c>Demo()</c> returns "List Dictionary Count".</example>
public static string Demo() =>
$"{nameof(List<>)} {nameof(Dictionary<,>)} {nameof(List<>.Count)}";
}
nameof never includes the type arguments or the arity. Use typeof(List<>).Name to get List`1.nameof evaluated at run time?Arrays, Span<T>, ReadOnlySpan<T> and string now convert to spans as built-in language conversions. Three new things follow. Extension methods on spans work on arrays. Generic type inference sees through the conversion. And arrays convert covariantly, so string[] becomes ReadOnlySpan<object>.
The BCL adds span overloads everywhere for speed. Before C# 14, callers often had to write .AsSpan() to reach them. Now the fast overload is picked by default.
Reading the figure. Blue boxes are what you hold. Amber is the writable span. Green is the read-only span, which every arrow can reach. Because these are language conversions now, the compiler uses them when it looks for extension methods and infers T.
public static class V14SpanExtensions
{
/// <summary>Counts even numbers in a read-only span.</summary>
/// <param name="items">The numbers.</param>
/// <returns>How many are even.</returns>
/// <example><c>new[] { 1, 2, 4 }.CountEven()</c> returns 2.</example>
public static int CountEven(this ReadOnlySpan<int> items)
{
int count = 0;
foreach (int x in items)
{
if (x % 2 == 0) count++; // % 2 == 0: even
}
return count;
}
/// <summary>Generic span method. C# 14 infers T from an array argument.</summary>
/// <param name="items">The items.</param>
/// <returns>The first item.</returns>
/// <example><c>FirstOf(new[] { "x" })</c> returns "x".</example>
public static T FirstOf<T>(ReadOnlySpan<T> items) => items[0]; // 0: first slot
/// <summary>Counts items in a span of objects.</summary>
/// <param name="items">Any objects.</param>
/// <returns>The length.</returns>
/// <example>A string[] of 2 converts covariantly and returns 2.</example>
public static int CountObjects(ReadOnlySpan<object> items) => items.Length;
/// <summary>Uses each new conversion once.</summary>
/// <returns>The three results joined by spaces.</returns>
/// <example><c>Demo()</c> returns "2 x 2".</example>
public static string Demo()
{
int[] numbers = [1, 2, 4];
string[] words = ["x", "y"];
int evens = numbers.CountEven(); // extension on span, called on an array
string first = FirstOf(words); // T inferred as string
int count = CountObjects(words); // string[] to ReadOnlySpan<object>
return $"{evens} {first} {count}";
}
}
IEnumerable<T> overload may now bind to a span overload. A known case is array.Contains(x) inside an expression tree, which can break some LINQ providers.Span<object> from a string[] throws ArrayTypeMismatchException. Only the read-only form is safe.numbers.CountEven() fail before C# 14?Lambda parameters can carry ref, out, in, scoped or ref readonly without spelling out their types. The types come from the target delegate.
Before, one modifier forced you to type every parameter, as in (string text, out int result) => ....
public delegate bool V14TryParse<T>(string text, out T result);
public delegate void V14Bump(ref int value);
public static class V14LambdaModifiers
{
/// <summary>Uses out and ref on untyped lambda parameters.</summary>
/// <returns>The parsed number and the bumped value.</returns>
/// <example><c>Demo()</c> returns "42 True 6".</example>
public static string Demo()
{
// text and result get their types from V14TryParse<int>.
V14TryParse<int> parse = (text, out result) => int.TryParse(text, out result);
bool ok = parse("42", out int n);
V14Bump twice = (ref value) => value *= 2; // * 2: doubles in place
int x = 3; // 3: sample input
twice(ref x);
return $"{n} {ok} {x}";
}
}
params still needs a type. It is the one modifier left out.var f = (ref x) => ... has no delegate to infer from.Instance constructors and events can now be partial. One part declares the signature. The other part supplies the body, or the add and remove accessors.
Source generators need it. You declare what you want, and a generator writes the code. C# 9 did this for methods, and C# 13 for properties and indexers. Constructors and events were the last members missing. See A 09 for generators.
// The "declaring" part, the side a human writes.
public partial class V14Widget
{
public partial V14Widget(string name);
public partial event Action<string>? Renamed;
}
// The "implementing" part, normally produced by a source generator.
public partial class V14Widget
{
private Action<string>? _renamed;
public string Name { get; private set; }
public partial V14Widget(string name) { Name = name; }
public partial event Action<string>? Renamed
{
add => _renamed += value;
remove => _renamed -= value;
}
/// <summary>Changes the name and raises Renamed.</summary>
/// <param name="name">The new name.</param>
/// <example><c>w.Rename("b")</c> raises Renamed with "b".</example>
public void Rename(string name)
{
Name = name;
_renamed?.Invoke(name);
}
/// <summary>Builds a widget, subscribes, renames.</summary>
/// <returns>The name the handler saw.</returns>
/// <example><c>V14Widget.Demo()</c> returns "beta".</example>
public static string Demo()
{
var w = new V14Widget("alpha");
string seen = "";
w.Renamed += n => seen = n;
w.Rename("beta");
return seen;
}
}
this(...) or base(...) initializer.A type can now define operator +=, -= and friends, and prefix ++ and --, as instance methods that return void. They change the object in place.
Before, a += b always meant a = a + b. That builds a new object. For big mutable values such as a matrix or a buffer, the copy is wasted work.
public sealed class V14Accumulator
{
public long Total { get; private set; }
public int Count { get; private set; }
// Instance compound operator: changes this object, no new instance.
public void operator +=(long value)
{
Total += value;
Count++;
}
// Instance increment: in-place +1.
public void operator ++()
{
Total++;
Count++;
}
/// <summary>Adds two values and one increment.</summary>
/// <returns>Total, count, and whether the object stayed the same.</returns>
/// <example><c>Demo()</c> returns "16 3 True".</example>
public static string Demo()
{
var acc = new V14Accumulator();
var same = acc;
acc += 10; // 10 and 5: sample amounts
acc += 5;
acc++; // +1, so the total is 16
return $"{acc.Total} {acc.Count} {ReferenceEquals(acc, same)}";
}
}
+= to make a new value.operator +, as before.var old = x++; needs the old value, so the compiler may fall back to the static operator.this. A static operator cannot change its operand in place without a ref, and it must return a new value.params works with spans and collection types, not only arrays. params ReadOnlySpan<T> avoids the array allocation. See A 08.
public static class V13Params
{
/// <summary>Sums any number of ints with no array allocation.</summary>
/// <param name="xs">The numbers.</param>
/// <returns>The sum.</returns>
/// <example><c>Sum(1, 2, 3)</c> returns 6.</example>
public static int Sum(params ReadOnlySpan<int> xs)
{
int total = 0;
foreach (int x in xs) total += x;
return total;
}
}
System.Threading.Lock, and lock uses EnterScope on it. See A 10.
public static class V13Lock
{
private static readonly Lock Gate = new();
private static int _hits;
/// <summary>Counts calls under the new Lock type.</summary>
/// <returns>The hit count after this call.</returns>
/// <example>First call returns 1.</example>
public static int Hit() { lock (Gate) { return ++_hits; } }
}
\e escape. A short form for the ESC character, U+001B, used in terminal colour codes.
public static class V13Escape
{
/// <summary>Code of the \e character.</summary>
/// <returns>27, the ESC code.</returns>
/// <example><c>Code()</c> returns 27.</example>
public static int Code() => "\e"[0]; // [0]: the only char
}
var f = Obj.Method;. It fixes some confusing errors. See A 03.
var f = Console.WriteLine; // still an error: too many applicable overloads
var g = Helper.Twice; // fine when exactly one candidate remains
^ from-end index works inside an initializer.
public sealed class V13Countdown
{
public int[] Slots { get; } = new int[3]; // 3 slots, 0..2
/// <summary>Sets the last slot with ^1 inside an initializer.</summary>
/// <returns>The value in slot 2.</returns>
/// <example><c>Demo()</c> returns 9.</example>
public static int Demo()
{
var c = new V13Countdown { Slots = { [^1] = 9 } }; // ^1: last slot. 9: sample
return c.Slots[2]; // 2: last index of 3 slots
}
}
await or yield. See A 05.
public static class V13RefInAsync
{
/// <summary>Bumps the first element through a ref local in an async method.</summary>
/// <param name="a">The array.</param>
/// <returns>The new first element.</returns>
/// <example><c>Bump([4])</c> returns 5.</example>
public static int Bump(int[] a) => BumpAsync(a).GetAwaiter().GetResult();
private static async Task<int> BumpAsync(int[] a)
{
ref int first = ref a[0]; // 0: first element
first++; // the ref is dead before the await
await Task.Yield();
return a[0];
}
}
Span<T>, and ref structs may implement interfaces. See A 02.
public interface IV13Sized { int Size { get; } }
public readonly ref struct V13Window(Span<int> data) : IV13Sized
{
private readonly Span<int> _data = data;
public int Size => _data.Length;
}
public static class V13AllowsRefStruct
{
/// <summary>Generic method that accepts a ref struct argument.</summary>
/// <param name="item">Anything sized, including ref structs.</param>
/// <returns>Its size.</returns>
/// <example><c>Demo()</c> returns 3.</example>
public static int SizeOf<T>(T item) where T : IV13Sized, allows ref struct => item.Size;
/// <summary>Passes a stack-only window to the generic method.</summary>
/// <returns>3, the window length.</returns>
/// <example><c>Demo()</c> returns 3.</example>
public static int Demo()
{
Span<int> buffer = stackalloc int[3]; // 3: small stack buffer
return SizeOf(new V13Window(buffer));
}
}
public partial class V13Tag
{
public partial string Label { get; set; }
}
public partial class V13Tag
{
private string _label = "";
public partial string Label { get => _label; set => _label = value.ToLowerInvariant(); }
/// <summary>Sets Label through the partial property.</summary>
/// <returns>The stored label.</returns>
/// <example><c>Demo()</c> returns "urgent".</example>
public static string Demo() => new V13Tag { Label = "URGENT" }.Label;
}
using System.Runtime.CompilerServices;
public static class V13Priority
{
/// <summary>Preferred overload: higher priority wins.</summary>
/// <param name="xs">The items.</param>
/// <returns>"span".</returns>
/// <example><c>Demo()</c> returns "span".</example>
[OverloadResolutionPriority(1)] // 1: above the default of 0
public static string Pick(ReadOnlySpan<int> xs) => "span";
/// <summary>Older overload, normally the better match for an array.</summary>
/// <param name="xs">The items.</param>
/// <returns>"array".</returns>
/// <example>Not chosen while the span overload has higher priority.</example>
public static string Pick(int[] xs) => "array";
/// <summary>Calls Pick with an array.</summary>
/// <returns>"span".</returns>
/// <example><c>Demo()</c> returns "span".</example>
public static string Demo()
{
int[] data = [1, 2];
return Pick(data);
}
}
public sealed class V12Greeter(string name)
{
/// <summary>Uses the captured primary constructor parameter.</summary>
/// <returns>The greeting.</returns>
/// <example><c>new V12Greeter("Bo").Greet()</c> returns "Hi Bo".</example>
public string Greet() => $"Hi {name}";
}
[a, b, ..rest] syntax for arrays, lists, spans and sets. See A 04.
public static class V12Collections
{
/// <summary>Builds a list from literals and a spread.</summary>
/// <returns>The joined list.</returns>
/// <example><c>Demo()</c> returns [0, 1, 2, 3].</example>
public static List<int> Demo()
{
int[] tail = [2, 3];
return [0, 1, .. tail]; // .. spreads tail in place
}
}
[System.Runtime.CompilerServices.InlineArray(4)] // 4 ints stored inline
public struct V12Four
{
private int _element0;
/// <summary>Fills slots with squares and sums them.</summary>
/// <returns>0 + 1 + 4 + 9 = 14.</returns>
/// <example><c>V12Four.Demo()</c> returns 14.</example>
public static int Demo()
{
var buf = new V12Four();
// i is the slot. 4 is the inline length.
for (int i = 0; i < 4; i++) buf[i] = i * i;
Span<int> view = buf; // inline arrays convert to spans
int sum = 0;
foreach (int v in view) sum += v;
return sum;
}
}
public static class V12LambdaDefaults
{
/// <summary>Calls a lambda with and without its optional argument.</summary>
/// <returns>"6 15".</returns>
/// <example><c>Demo()</c> returns "6 15".</example>
public static string Demo()
{
var times3 = (int x = 2) => x * 3; // 2: the default. 3: the factor
return $"{times3()} {times3(5)}"; // 5: sample argument
}
}
public static class V12RefReadonly
{
/// <summary>Reads a value passed by read-only reference.</summary>
/// <param name="x">The value. Cannot be changed here.</param>
/// <returns>x + 1.</returns>
/// <example><c>Demo()</c> returns 8.</example>
public static int Next(ref readonly int x) => x + 1; // + 1: the next value
/// <summary>Calls Next with in.</summary>
/// <returns>8.</returns>
/// <example><c>Demo()</c> returns 8.</example>
public static int Demo()
{
int seven = 7; // 7: sample value
return Next(in seven);
}
}
using aliases now accept tuples, arrays and pointers.
using V12Pair = (int Left, int Right);
public static class V12Alias
{
/// <summary>Uses a tuple alias as a parameter type.</summary>
/// <param name="p">The pair.</param>
/// <returns>The sum.</returns>
/// <example><c>Sum((2, 3))</c> returns 5.</example>
public static int Sum(V12Pair p) => p.Left + p.Right;
}
[Experimental("ID")] makes every use an error unless the caller opts in. Shown as a fragment because using it needs a suppression.
[Experimental("BP0001")]
public static class Preview { public static int Ping() => 1; }
// Preview.Ping(); // error BP0001 unless <NoWarn>BP0001</NoWarn>
[InterceptsLocation(version: 1, data: "...")] // data is produced by the generator
public static void FastLog(this ILogger log, string msg) { /* ... */ }
""" strings with no escaping, and indentation trimmed. See Guide 5.
public static class V11Raw
{
/// <summary>JSON with quotes and no escapes.</summary>
/// <returns>The raw text.</returns>
/// <example><c>Json()</c> returns "{\"id\": 1}".</example>
public static string Json() => """{"id": 1}""";
}
using System.Numerics;
public static class V11Math
{
/// <summary>Sums any numeric type.</summary>
/// <param name="xs">The numbers.</param>
/// <returns>The sum.</returns>
/// <example><c>Sum([1.5, 2.5])</c> returns 4.</example>
public static T Sum<T>(T[] xs) where T : INumber<T>
{
T total = T.Zero; // T.Zero: a static abstract member
foreach (T x in xs) total += x;
return total;
}
}
class FooAttribute<T> : Attribute replaces typeof arguments.
[AttributeUsage(AttributeTargets.Class)]
public sealed class V11KindAttribute<T> : Attribute
{
public Type Kind => typeof(T);
}
[V11Kind<int>]
public sealed class V11Tagged
{
/// <summary>Reads the type argument of the attribute.</summary>
/// <returns>"Int32".</returns>
/// <example><c>Demo()</c> returns "Int32".</example>
public static string Demo() =>
typeof(V11Tagged).GetCustomAttributes(false).OfType<V11KindAttribute<int>>()
.Single().Kind.Name;
}
"text"u8 is a ReadOnlySpan<byte> built at compile time. See A 08.
public static class V11Utf8
{
/// <summary>Byte length of a UTF-8 literal.</summary>
/// <returns>3: "é" is two bytes, "a" is one.</returns>
/// <example><c>Length()</c> returns 3.</example>
public static int Length() => "éa"u8.Length;
}
switch expression can span lines inside { }.
public static class V11Holes
{
/// <summary>Formats a sign with a multi-line hole.</summary>
/// <param name="x">A number.</param>
/// <returns>"sign: pos" or "sign: non-pos".</returns>
/// <example><c>Sign(4)</c> returns "sign: pos".</example>
public static string Sign(int x) => $"sign: {x switch
{
> 0 => "pos", // 0: the boundary
_ => "non-pos",
}}";
}
[first, .., last]. See A 06.
public static class V11ListPatterns
{
/// <summary>Describes a list by its shape.</summary>
/// <param name="xs">The list.</param>
/// <returns>A short description.</returns>
/// <example><c>Shape([1, 5, 9])</c> returns "1..9".</example>
public static string Shape(int[] xs) => xs switch
{
[] => "empty",
[var only] => $"one {only}",
[var first, .., var last] => $"{first}..{last}",
};
}
file class is visible only in its source file. Generators use it to avoid name clashes.
file static class V11Hidden
{
public static int Seven => 7; // 7: sample constant
}
public static class V11FileLocal
{
/// <summary>Reads a value from a file-local type.</summary>
/// <returns>7.</returns>
/// <example><c>Demo()</c> returns 7.</example>
public static int Demo() => V11Hidden.Seven;
}
required forces callers to set a property in the initializer. See A 07.
public sealed class V11User
{
public required string Email { get; init; }
/// <summary>Creates a user. Leaving Email out would not compile.</summary>
/// <returns>The email.</returns>
/// <example><c>Demo()</c> returns "a@b.c".</example>
public static string Demo() => new V11User { Email = "a@b.c" }.Email;
}
public struct V11Point
{
public int X;
public int Y;
public V11Point(int x) { X = x; } // Y is zeroed for you
/// <summary>Builds a point with only X set.</summary>
/// <returns>"5,0".</returns>
/// <example><c>Demo()</c> returns "5,0".</example>
public static string Demo()
{
var p = new V11Point(5); // 5: sample X
return $"{p.X},{p.Y}";
}
}
Span<char> on a constant string.
public static class V11SpanMatch
{
/// <summary>Matches a span against string constants.</summary>
/// <param name="text">Input text.</param>
/// <returns>true for "yes" or "y".</returns>
/// <example><c>IsYes("yes")</c> returns true.</example>
public static bool IsYes(string text)
{
ReadOnlySpan<char> s = text.AsSpan().Trim();
return s is "yes" or "y";
}
}
nameof inside attributes on the method.
[AttributeUsage(AttributeTargets.Method)]
public sealed class V11NoteAttribute(string text) : Attribute
{
public string Text { get; } = text;
}
public static class V11NameofScope
{
/// <summary>Method whose attribute names its own parameter.</summary>
/// <param name="value">Any number.</param>
/// <returns>The same number.</returns>
/// <example><c>Echo(3)</c> returns 3.</example>
[V11Note(nameof(value))]
public static int Echo(int value) => value;
/// <summary>Reads the attribute text back.</summary>
/// <returns>"value".</returns>
/// <example><c>Demo()</c> returns "value".</example>
public static string Demo() =>
typeof(V11NameofScope).GetMethod(nameof(Echo))!
.GetCustomAttributes(false).OfType<V11NoteAttribute>().Single().Text;
}
public static class V11NativeInt
{
/// <summary>Shows nint and IntPtr are the same type.</summary>
/// <returns>true.</returns>
/// <example><c>Same()</c> returns true.</example>
public static bool Same() => typeof(nint) == typeof(IntPtr);
}
ref struct can hold a ref field. scoped stops a ref from escaping. See A 01.
public ref struct V11RefBox
{
private ref int _target; // a ref field, new in C# 11
public V11RefBox(ref int target) { _target = ref target; }
/// <summary>Adds 1 to the referenced variable.</summary>
/// <example>x = 1, after Bump x is 2.</example>
public void Bump() => _target++;
/// <summary>Sums a span that may not escape this call.</summary>
/// <param name="s">The numbers.</param>
/// <returns>The sum.</returns>
/// <example><c>SumScoped([1, 2])</c> returns 3.</example>
public static int SumScoped(scoped ReadOnlySpan<int> s)
{
int t = 0;
foreach (int v in s) t += v;
return t;
}
/// <summary>Changes a local through the box.</summary>
/// <returns>2.</returns>
/// <example><c>Demo()</c> returns 2.</example>
public static int Demo()
{
int x = 1; // 1: start value
var box = new V11RefBox(ref x);
box.Bump();
return x;
}
}
operator checked + runs inside checked(...).
public readonly record struct V11Cents(int Value)
{
public static V11Cents operator +(V11Cents a, V11Cents b) => new(unchecked(a.Value + b.Value));
public static V11Cents operator checked +(V11Cents a, V11Cents b) =>
new(checked(a.Value + b.Value));
/// <summary>Adds past int.MaxValue in a checked context.</summary>
/// <returns>"overflow".</returns>
/// <example><c>Demo()</c> returns "overflow".</example>
public static string Demo()
{
try
{
// int.MaxValue + 1 does not fit, so the checked operator throws.
_ = checked(new V11Cents(int.MaxValue) + new V11Cents(1));
return "no overflow";
}
catch (OverflowException) { return "overflow"; }
}
}
>>>. Shifts in zeros even for signed types. See Pattern 19.
public static class V11UnsignedShift
{
/// <summary>Shifts -8 right by 28 with zero fill.</summary>
/// <returns>15: the top four bits of 0xFFFFFFF8.</returns>
/// <example><c>Demo()</c> returns 15.</example>
public static int Demo() => -8 >>> 28; // 28 = 32 - 4, keeps the top 4 bits
}
int.
public static Bits operator <<(Bits b, Bits count) => ...; // was an error before C# 11
list.ForEach(Print) for a static method now reuses one delegate instead of allocating each time.
items.ForEach(Print); // C# 11: the delegate for static Print is cached
class record { } // warning CS8981
record struct gives value equality, with, and ToString to a struct. See A 06.
public readonly record struct V10Point(int X, int Y)
{
/// <summary>Copies a point with one field changed.</summary>
/// <returns>The printed copy.</returns>
/// <example><c>Demo()</c> returns "V10Point { X = 1, Y = 5 }".</example>
public static string Demo() => (new V10Point(1, 2) with { Y = 5 }).ToString();
}
public struct V10Counter
{
public int Start = 10; // 10: sample start value
public V10Counter() { }
/// <summary>new() runs the constructor, default does not.</summary>
/// <returns>"10 0".</returns>
/// <example><c>Demo()</c> returns "10 0".</example>
public static string Demo() => $"{new V10Counter().Start} {default(V10Counter).Start}";
}
$"..." pieces directly. Logging skips formatting when it is off. See A 08.
[InterpolatedStringHandler]
public ref struct LogHandler { /* AppendLiteral, AppendFormatted ... */ }
public void Debug(ref LogHandler msg) { } // $"..." formats only if enabled
global using line applies to the whole project. ImplicitUsings is built on it.
global using System.Collections.Concurrent;
namespace X; saves one level of indent. This page's harness adds one for you.
namespace CsI.A11;
public static class Thing { }
{ Address.City: "Oslo" } instead of nested braces. See A 06.
public sealed record V10Address(string City);
public sealed record V10Customer(string Name, V10Address Address)
{
/// <summary>Checks a nested property with a dotted pattern.</summary>
/// <returns>true for a customer in Oslo.</returns>
/// <example><c>Demo()</c> returns true.</example>
public static bool Demo() => new V10Customer("Ida", new("Oslo")) is { Address.City: "Oslo" };
}
var, an explicit return type, and attributes. See A 03.
public static class V10Lambdas
{
/// <summary>Uses var with a lambda and an explicit return type.</summary>
/// <returns>"43 one".</returns>
/// <example><c>Demo()</c> returns "43 one".</example>
public static string Demo()
{
var parse = (string s) => int.Parse(s) + 1; // natural type Func<string, int>
var choose = object (bool b) => b ? "one" : 1; // explicit return type object
return $"{parse("42")} {choose(true)}";
}
}
const strings can use $"..." when all parts are constant strings.
public static class V10ConstInterp
{
private const string Site = "bp";
public const string Banner = $"site: {Site}";
}
ToString so derived records keep it.
public record V10Base
{
public sealed override string ToString() => "base";
}
public sealed record V10Derived : V10Base
{
/// <summary>The derived record cannot override ToString again.</summary>
/// <returns>"base".</returns>
/// <example><c>Demo()</c> returns "base".</example>
public static string Demo() => new V10Derived().ToString();
}
public static class V10Deconstruct
{
/// <summary>Assigns one existing and one new variable in one step.</summary>
/// <returns>"1 2".</returns>
/// <example><c>Demo()</c> returns "1 2".</example>
public static string Demo()
{
int x;
(x, int y) = (1, 2); // x existed, y is new
return $"{x} {y}";
}
}
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
public async ValueTask<int> ReadAsync() { ... }
using System.Runtime.CompilerServices;
public static class V10CallerExpr
{
/// <summary>Reports the failing expression text.</summary>
/// <param name="ok">The condition.</param>
/// <param name="expr">Filled in by the compiler.</param>
/// <returns>"ok" or "failed: ...".</returns>
/// <example><c>Check(1 > 2)</c> returns "failed: 1 > 2".</example>
public static string Check(bool ok, [CallerArgumentExpression(nameof(ok))] string expr = "")
=> ok ? "ok" : $"failed: {expr}";
/// <summary>Calls Check with a false condition.</summary>
/// <returns>"failed: x > 5".</returns>
/// <example><c>Demo()</c> returns "failed: x > 5".</example>
public static string Demo()
{
int x = 3; // 3: below the limit of 5
return Check(x > 5);
}
}
out variables from ?. calls are known to be set after == true.
public static class V10DefiniteAssign
{
/// <summary>Uses an out variable after a null-conditional call.</summary>
/// <returns>1, the stored value.</returns>
/// <example><c>Demo()</c> returns 1.</example>
public static int Demo()
{
Dictionary<string, int>? map = new() { ["a"] = 1 }; // 1: sample value
if (map?.TryGetValue("a", out int v) == true) return v; // C# 10 knows v is set
return -1; // -1: not found
}
}
#line.
#line (1, 1) - (1, 20) 5 "page.razor"
with on structs and anonymous types.
public static class V10WithAnon
{
/// <summary>Copies an anonymous object with one change.</summary>
/// <returns>"1 3".</returns>
/// <example><c>Demo()</c> returns "1 3".</example>
public static string Demo()
{
var p = new { A = 1, B = 2 };
var q = p with { B = 3 }; // 3: the new B
return $"{q.A} {q.B}";
}
}
with and a readable ToString. See A 06.
public sealed record V9Person(string First, string Last)
{
/// <summary>Two records with equal data are equal.</summary>
/// <returns>true.</returns>
/// <example><c>Demo()</c> returns true.</example>
public static bool Demo() => new V9Person("A", "B") == new V9Person("A", "B");
}
public sealed class V9Config
{
public int Port { get; init; } = 80; // 80: default HTTP port
/// <summary>Sets Port once in the initializer.</summary>
/// <returns>8080.</returns>
/// <example><c>Demo()</c> returns 8080.</example>
public static int Demo() => new V9Config { Port = 8080 }.Port; // 8080: sample port
}
Main method. Fragment: it must be the one entry file.
Console.WriteLine("Hello"); // the whole Program.cs
< >, and the and, or, not combinators. See A 06.
public static class V9Patterns
{
/// <summary>Classifies a number with relational patterns.</summary>
/// <param name="n">A number.</param>
/// <returns>A label.</returns>
/// <example><c>Size(7)</c> returns "small".</example>
public static string Size(int n) => n switch
{
< 0 => "negative",
0 => "zero",
> 0 and < 10 => "small", // 10: first two-digit number
_ => "big",
};
}
nint and nuint, 32 or 64 bits by platform.
public static class V9Nint
{
/// <summary>Size of nint on this machine.</summary>
/// <returns>8 on a 64-bit process.</returns>
/// <example><c>Bytes()</c> returns 8 on x64 or ARM64.</example>
public static int Bytes() => IntPtr.Size;
}
delegate* calls a static method with no delegate allocation. See A 08.
public static unsafe class V9FnPtr
{
private static int Twice(int x) => x * 2; // * 2: doubles
/// <summary>Calls Twice through a function pointer.</summary>
/// <returns>42.</returns>
/// <example><c>Demo()</c> returns 42.</example>
public static int Demo()
{
delegate*<int, int> f = &Twice;
return f(21); // 21 * 2 = 42
}
}
stackalloc memory, for hot code. See A 08.
public static class V9SkipInit
{
/// <summary>Uses an uninitialized stack buffer, then fills it.</summary>
/// <returns>1, from the filled buffer.</returns>
/// <example><c>Demo()</c> returns 1.</example>
[System.Runtime.CompilerServices.SkipLocalsInit]
public static int Demo()
{
Span<int> s = stackalloc int[4]; // 4: small buffer, contents unknown
s.Fill(1); // must write before reading
return s[3]; // 3: last slot
}
}
new. Leave out the type when the target already names it.
public static class V9TargetNew
{
/// <summary>Creates a list with new().</summary>
/// <returns>2.</returns>
/// <example><c>Demo()</c> returns 2.</example>
public static int Demo()
{
List<int> xs = new() { 1, 2 };
return xs.Count;
}
}
static x => ... cannot capture, so it never allocates a closure. See A 03.
public static class V9StaticLambda
{
/// <summary>A static lambda, guaranteed capture-free.</summary>
/// <returns>6.</returns>
/// <example><c>Demo()</c> returns 6.</example>
public static int Demo()
{
Func<int, int> inc = static x => x + 1; // + 1: increment
return inc(5); // 5: sample input
}
}
b ? 1 : null works when the target is int?.
public static class V9TargetCond
{
/// <summary>Mixes int and null in a conditional.</summary>
/// <param name="has">Whether a value exists.</param>
/// <returns>1 or null.</returns>
/// <example><c>Maybe(false)</c> returns null.</example>
public static int? Maybe(bool has) => has ? 1 : null;
}
public abstract class V9Animal
{
public abstract V9Animal Clone();
}
public sealed class V9Cat : V9Animal
{
public override V9Cat Clone() => new(); // V9Cat, not V9Animal
/// <summary>Clone returns V9Cat with no cast.</summary>
/// <returns>"V9Cat".</returns>
/// <example><c>Demo()</c> returns "V9Cat".</example>
public static string Demo() => new V9Cat().Clone().GetType().Name;
}
public static class V9RangeLoop
{
/// <summary>Lets foreach walk a Range, end exclusive.</summary>
/// <param name="r">The range.</param>
/// <returns>An enumerator over the ints.</returns>
/// <example><c>foreach (var i in 1..4)</c> yields 1, 2, 3.</example>
public static IEnumerator<int> GetEnumerator(this Range r)
{
for (int i = r.Start.Value; i < r.End.Value; i++) yield return i;
}
/// <summary>Sums 1..4 with foreach.</summary>
/// <returns>6.</returns>
/// <example><c>Demo()</c> returns 6.</example>
public static int Demo()
{
int sum = 0;
foreach (int i in 1..4) sum += i; // 1, 2, 3
return sum;
}
}
(_, _) => 0 for parameters you ignore.
public static class V9Discards
{
/// <summary>A two-argument lambda that ignores both.</summary>
/// <returns>0.</returns>
/// <example><c>Demo()</c> returns 0.</example>
public static int Demo()
{
Func<int, int, int> zero = (_, _) => 0;
return zero(1, 2);
}
}
public static class V9LocalAttr
{
/// <summary>Calls an attributed local function.</summary>
/// <returns>5.</returns>
/// <example><c>Demo()</c> returns 5.</example>
public static int Demo()
{
[System.Diagnostics.DebuggerStepThrough]
static int Five() => 5;
return Five();
}
}
[ModuleInitializer] runs once when the assembly loads. See A 09.
public static class V9Module
{
public static bool Ran;
[System.Runtime.CompilerServices.ModuleInitializer]
internal static void Init() => Ran = true;
}
out parameters and have access modifiers, if they have a body.
public partial class V9Gen
{
public partial int Answer();
}
public partial class V9Gen
{
/// <summary>Implemented part of a partial method that returns a value.</summary>
/// <returns>42.</returns>
/// <example><c>new V9Gen().Answer()</c> returns 42.</example>
public partial int Answer() => 42; // 42: sample answer
}
readonly so it promises not to change state. See A 01.
public struct V8Vec
{
public double X, Y;
/// <summary>Length, without copying or changing the struct.</summary>
/// <example>X 3, Y 4 gives 5.</example>
public readonly double Length => Math.Sqrt(X * X + Y * Y);
}
public interface IV8Greeter
{
string Name { get; }
string Greet() => $"Hello, {Name}";
}
public sealed class V8Bob : IV8Greeter
{
public string Name => "Bob";
/// <summary>Calls the default method through the interface.</summary>
/// <returns>"Hello, Bob".</returns>
/// <example><c>Demo()</c> returns "Hello, Bob".</example>
public static string Demo() => ((IV8Greeter)new V8Bob()).Greet();
}
x switch { ... } returns a value. See A 06.
public static class V8Switch
{
/// <summary>Maps a day number to a type of day.</summary>
/// <param name="day">1 = Monday ... 7 = Sunday.</param>
/// <returns>"weekend" or "weekday".</returns>
/// <example><c>Kind(6)</c> returns "weekend".</example>
public static string Kind(int day) => day switch
{
6 or 7 => "weekend", // 6, 7: Saturday, Sunday
_ => "weekday",
};
}
{ Length: 0 }.
public static class V8PropertyPattern
{
/// <summary>Checks emptiness with a property pattern.</summary>
/// <param name="s">Any string or null.</param>
/// <returns>true for "".</returns>
/// <example><c>IsEmpty("")</c> returns true.</example>
public static bool IsEmpty(string? s) => s is { Length: 0 };
}
public static class V8TuplePattern
{
/// <summary>Rock, paper, scissors from player A's view.</summary>
/// <param name="a">A's move.</param>
/// <param name="b">B's move.</param>
/// <returns>"win", "lose" or "draw".</returns>
/// <example><c>Play("rock", "scissors")</c> returns "win".</example>
public static string Play(string a, string b) => (a, b) switch
{
_ when a == b => "draw",
("rock", "scissors") or ("paper", "rock") or ("scissors", "paper") => "win",
_ => "lose",
};
}
(0, 0).
public readonly record struct V8Pt(int X, int Y)
{
/// <summary>Describes a point by position.</summary>
/// <returns>"origin", "x-axis", "y-axis" or "plane".</returns>
/// <example><c>new V8Pt(0, 3).Where()</c> returns "y-axis".</example>
public string Where() => this switch
{
(0, 0) => "origin",
(_, 0) => "x-axis",
(0, _) => "y-axis",
_ => "plane",
};
}
using var x = ...; disposes at the end of the scope.
public static class V8UsingDecl
{
/// <summary>Writes to a writer disposed at scope end.</summary>
/// <returns>"done".</returns>
/// <example><c>Demo()</c> returns "done".</example>
public static string Demo()
{
using var w = new StringWriter();
w.Write("done");
return w.ToString();
}
}
public static class V8StaticLocal
{
/// <summary>Squares through a static local function.</summary>
/// <param name="n">The number.</param>
/// <returns>n squared.</returns>
/// <example><c>Square(4)</c> returns 16.</example>
public static int Square(int n)
{
static int Sq(int x) => x * x;
return Sq(n);
}
}
ref struct with a Dispose method works in using without an interface.
public ref struct V8Scope
{
public static int Closed;
/// <summary>Marks the scope as closed.</summary>
/// <example>After a using block, Closed rises by 1.</example>
public void Dispose() => Closed++;
/// <summary>Uses the ref struct in a using statement.</summary>
/// <returns>Closed after the block, which is 1 on the first call.</returns>
/// <example><c>Demo()</c> returns 1 the first time.</example>
public static int Demo()
{
using (new V8Scope()) { }
return Closed;
}
}
string? versus string, with compile-time warnings. See A 07.
public static class V8Nullable
{
/// <summary>Handles a maybe-null string safely.</summary>
/// <param name="s">A string or null.</param>
/// <returns>Its length, or 0 for null.</returns>
/// <example><c>Len(null)</c> returns 0.</example>
public static int Len(string? s) => s?.Length ?? 0;
}
IAsyncEnumerable<T> and await foreach. See A 05.
public static class V8AsyncStream
{
private static async IAsyncEnumerable<int> Numbers(int n)
{
for (int i = 1; i <= n; i++) // 1..n inclusive
{
await Task.Yield();
yield return i;
}
}
/// <summary>Sums an async stream.</summary>
/// <param name="n">How many numbers.</param>
/// <returns>1 + ... + n.</returns>
/// <example><c>Sum(4)</c> returns 10.</example>
public static int Sum(int n) => SumAsync(n).GetAwaiter().GetResult();
private static async Task<int> SumAsync(int n)
{
int total = 0;
await foreach (int x in Numbers(n)) total += x;
return total;
}
}
^1 is the last item and 1..^1 slices. See Guide 1.
public static class V8Ranges
{
/// <summary>Last item and the middle slice.</summary>
/// <returns>"5 [2, 3, 4]".</returns>
/// <example><c>Demo()</c> returns "5 [2, 3, 4]".</example>
public static string Demo()
{
int[] xs = [1, 2, 3, 4, 5];
int[] middle = xs[1..^1]; // drop first and last
return $"{xs[^1]} [{string.Join(", ", middle)}]";
}
}
x ??= value assigns only when x is null.
public static class V8CoalesceAssign
{
/// <summary>Creates a list only if missing.</summary>
/// <returns>1.</returns>
/// <example><c>Demo()</c> returns 1.</example>
public static int Demo()
{
List<int>? xs = null;
xs ??= [];
xs.Add(7); // 7: sample item
xs ??= [1, 2, 3]; // ignored: xs is not null
return xs.Count;
}
}
stackalloc and pointers accept it.
public struct V8Pair<T> where T : unmanaged
{
public T A, B;
/// <summary>stackalloc of a generic struct.</summary>
/// <returns>3.</returns>
/// <example><c>V8Pair<int>.Demo()</c> returns 3.</example>
public static int Demo()
{
Span<V8Pair<int>> pairs = stackalloc V8Pair<int>[2]; // 2: two pairs
pairs[1].B = 3; // 3: sample value
return pairs[1].B;
}
}
public static class V8NestedStackalloc
{
/// <summary>Finds a CR or LF with an inline stackalloc set.</summary>
/// <returns>1, the index of 13.</returns>
/// <example><c>Demo()</c> returns 1.</example>
public static int Demo()
{
ReadOnlySpan<int> codes = [65, 13, 10]; // 'A', CR, LF
return codes.IndexOfAny(stackalloc[] { 13, 10 }); // 13 = CR, 10 = LF
}
}
$@ and @$ both work. Either order for verbatim interpolated strings.
public static class V8VerbatimInterp
{
/// <summary>Builds a Windows path with @$.</summary>
/// <param name="name">File name.</param>
/// <returns>The path.</returns>
/// <example><c>Path("a.txt")</c> returns "C:\\tmp\\a.txt".</example>
public static string Path(string name) => @$"C:\tmp\{name}";
}
== and != compare tuples element by element.
public static class V73TupleEq
{
/// <summary>Compares two tuples.</summary>
/// <returns>true.</returns>
/// <example><c>Demo()</c> returns true.</example>
public static bool Demo() => (1, "a") == (1, "a");
}
r = ref other; points an existing ref at a new variable.
public static class V73RefReassign
{
/// <summary>Moves a ref from a to b, then writes through it.</summary>
/// <returns>"1 9".</returns>
/// <example><c>Demo()</c> returns "1 9".</example>
public static string Demo()
{
int a = 1, b = 2;
ref int r = ref a;
r = ref b; // now r aliases b
r = 9; // 9: sample value
return $"{a} {b}";
}
}
public static class V73StackallocInit
{
/// <summary>Sums a stack buffer filled at creation.</summary>
/// <returns>6.</returns>
/// <example><c>Demo()</c> returns 6.</example>
public static int Demo()
{
Span<int> s = stackalloc int[] { 1, 2, 3 };
return s[0] + s[1] + s[2];
}
}
fixed buffer in a movable object without a fixed statement. See A 01.
public unsafe struct V73Buffer
{
public fixed int Slots[4]; // 4 ints inline
}
public sealed class V73Holder
{
public V73Buffer Buf;
/// <summary>Writes and reads a fixed buffer inside a class.</summary>
/// <returns>7.</returns>
/// <example><c>Demo()</c> returns 7.</example>
public static unsafe int Demo()
{
var h = new V73Holder();
h.Buf.Slots[2] = 7; // 2: third slot. 7: sample
return h.Buf.Slots[2];
}
}
[field: ...] on an auto-property.
using System.Reflection;
public sealed class V73Fields
{
[field: NonSerialized]
public int Cache { get; set; }
/// <summary>Checks the backing field got the attribute.</summary>
/// <returns>true.</returns>
/// <example><c>Demo()</c> returns true.</example>
public static bool Demo() =>
typeof(V73Fields).GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.Single().IsNotSerialized;
}
out var in field initializers and constructor initializers.
public static class V73ExprVars
{
// 12 parses and is above the limit of 10.
public static readonly bool Ok = int.TryParse("12", out var n) && n > 10;
}
// A generic overload whose constraint fails is no longer considered at all.
Enum, Delegate and unmanaged. See A 02.
public static class V73Constraints
{
/// <summary>Names of any enum type.</summary>
/// <returns>The names.</returns>
/// <example><c>Names<DayOfWeek>().Length</c> returns 7.</example>
public static string[] Names<T>() where T : struct, Enum => Enum.GetNames<T>();
/// <summary>Size of any unmanaged type.</summary>
/// <returns>The size in bytes.</returns>
/// <example><c>SizeOf<long>()</c> returns 8.</example>
public static unsafe int SizeOf<T>() where T : unmanaged => sizeof(T);
}
in parameters. Pass a big struct by read-only reference. See A 01.
public static class V72In
{
/// <summary>Reads a struct passed by in.</summary>
/// <param name="v">The vector, not copied.</param>
/// <returns>X + Y.</returns>
/// <example><c>Sum(new V72Vec(1, 2))</c> returns 3.</example>
public static double Sum(in V72Vec v) => v.X + v.Y;
}
readonly struct. Every field is read-only, so the compiler never makes defensive copies.
public readonly struct V72Vec(double x, double y)
{
public double X { get; } = x;
public double Y { get; } = y;
}
ref readonly returns. Return a reference the caller may not write through.
public static class V72RefReadonlyReturn
{
/// <summary>Reference to the largest element.</summary>
/// <param name="a">A non-empty array.</param>
/// <returns>A read-only ref to the max.</returns>
/// <example><c>Demo()</c> returns 9.</example>
public static ref readonly int Largest(int[] a)
{
int best = 0; // 0: index of the first element
for (int i = 1; i < a.Length; i++) // 1: index 0 is the starting best
{
if (a[i] > a[best]) best = i;
}
return ref a[best];
}
/// <summary>Reads the max through a ref readonly local.</summary>
/// <returns>9.</returns>
/// <example><c>Demo()</c> returns 9.</example>
public static int Demo()
{
int[] a = [4, 9, 2];
ref readonly int m = ref Largest(a);
return m;
}
}
ref struct. A stack-only type, the basis of Span<T>. See A 01.
public ref struct V72Cursor(ReadOnlySpan<char> text)
{
private readonly ReadOnlySpan<char> _text = text;
private int _pos;
/// <summary>Returns the next char, or '\0' at the end.</summary>
/// <returns>The char.</returns>
/// <example>On "ab", two calls return 'a' then 'b'.</example>
public char Next() => _pos < _text.Length ? _text[_pos++] : '\0';
}
public static class V72Named
{
private static string Make(string name, int size) => $"{name}:{size}";
/// <summary>Names the first argument, passes the second by position.</summary>
/// <returns>"box:3".</returns>
/// <example><c>Demo()</c> returns "box:3".</example>
public static string Demo() => Make(name: "box", 3);
}
0b_1010 and 0x_FF.
public static class V72Digits
{
/// <summary>A binary literal with a leading underscore.</summary>
/// <returns>10.</returns>
/// <example><c>Ten()</c> returns 10.</example>
public static int Ten() => 0b_1010;
}
private protected. Visible to derived classes in the same assembly only.
public class V72Base
{
private protected int Secret = 7; // 7: sample value
}
public sealed class V72Derived : V72Base
{
/// <summary>A derived class in the same assembly can read it.</summary>
/// <returns>7.</returns>
/// <example><c>new V72Derived().Read()</c> returns 7.</example>
public int Read() => Secret;
}
ref (c ? ref a : ref b) picks a variable, not a value.
public static class V72CondRef
{
/// <summary>Writes through a conditionally chosen ref.</summary>
/// <returns>"0 9".</returns>
/// <example><c>Demo()</c> returns "0 9".</example>
public static string Demo()
{
int a = 0, b = 0;
bool pickB = true;
ref int target = ref (pickB ? ref b : ref a);
target = 9; // 9: sample value
return $"{a} {b}";
}
}
static async Task Main() as the entry point. Fragment: one entry point per program. See A 05.
static async Task Main() => await RunAsync();
default literal. default without a type when the target is known.
public static class V71Default
{
/// <summary>A default parameter value written as default.</summary>
/// <param name="n">Optional, defaults to 0.</param>
/// <returns>n.</returns>
/// <example><c>Get()</c> returns 0.</example>
public static int Get(int n = default) => n;
}
(count, name) names its items count and name.
public static class V71TupleNames
{
/// <summary>Reads tuple items by their inferred names.</summary>
/// <returns>"2 x".</returns>
/// <example><c>Demo()</c> returns "2 x".</example>
public static string Demo()
{
int count = 2;
string name = "x";
var t = (count, name);
return $"{t.count} {t.name}";
}
}
item is int i where item is a T.
public static class V71GenericPattern
{
/// <summary>Type-tests a generic value.</summary>
/// <param name="item">Any value.</param>
/// <returns>A description.</returns>
/// <example><c>Describe(5)</c> returns "int 5".</example>
public static string Describe<T>(T item) => item is int i ? $"int {i}" : "other";
}
out var n.
public static class V70Out
{
/// <summary>Parses with an inline out variable.</summary>
/// <param name="s">Text.</param>
/// <returns>The number, or -1.</returns>
/// <example><c>Parse("12")</c> returns 12.</example>
public static int Parse(string s) => int.TryParse(s, out var n) ? n : -1; // -1: bad
}
(int min, int max) return types and var (a, b) = .... See Guide 1.
public static class V70Tuples
{
/// <summary>Returns min and max together.</summary>
/// <param name="xs">A non-empty array.</param>
/// <returns>The pair.</returns>
/// <example><c>MinMax([3, 1, 4])</c> returns (1, 4).</example>
public static (int Min, int Max) MinMax(int[] xs) => (xs.Min(), xs.Max());
/// <summary>Deconstructs the pair.</summary>
/// <returns>"1 4".</returns>
/// <example><c>Demo()</c> returns "1 4".</example>
public static string Demo()
{
var (lo, hi) = MinMax([3, 1, 4]);
return $"{lo} {hi}";
}
}
_ for values you do not need.
public static class V70Discards
{
/// <summary>Checks a parse without keeping the number.</summary>
/// <param name="s">Text.</param>
/// <returns>true if it is an int.</returns>
/// <example><c>IsInt("x")</c> returns false.</example>
public static bool IsInt(string s) => int.TryParse(s, out _);
}
is T x and case T x when .... See A 06.
public static class V70Patterns
{
/// <summary>Switch with type patterns and a when clause.</summary>
/// <param name="o">Any object.</param>
/// <returns>A label.</returns>
/// <example><c>Kind(-3)</c> returns "negative int".</example>
public static string Kind(object? o)
{
switch (o)
{
case int n when n < 0: return "negative int";
case int: return "int";
case string s: return $"string of {s.Length}";
default: return "other";
}
}
}
public static class V70LocalFn
{
/// <summary>Factorial with a recursive local function.</summary>
/// <param name="n">n >= 0.</param>
/// <returns>n!.</returns>
/// <example><c>Fact(5)</c> returns 120.</example>
public static long Fact(int n)
{
long Go(int k) => k <= 1 ? 1 : k * Go(k - 1); // 1: base case 0! = 1! = 1
return Go(n);
}
}
public static class V70RefReturn
{
private static ref int Slot(int[] a, int i) => ref a[i];
/// <summary>Writes through a returned ref.</summary>
/// <returns>"[0, 5]".</returns>
/// <example><c>Demo()</c> returns "[0, 5]".</example>
public static string Demo()
{
int[] a = [0, 0];
Slot(a, 1) = 5; // 1: second slot. 5: sample value
return $"[{string.Join(", ", a)}]";
}
}
=>.
public sealed class V70Bodies
{
private string _name;
public V70Bodies(string name) => _name = name;
public string Name { get => _name; set => _name = value; }
}
?? throw and ? : throw. See A 07.
public static class V70Throw
{
/// <summary>Returns the text or throws for null.</summary>
/// <param name="s">Text or null.</param>
/// <returns>The text.</returns>
/// <example><c>Need(null)</c> throws ArgumentNullException.</example>
public static string Need(string? s) => s ?? throw new ArgumentNullException(nameof(s));
}
ValueTask<T> and custom task types. See A 05.
public static class V70ValueTask
{
/// <summary>Completes synchronously with no Task allocation.</summary>
/// <returns>A finished ValueTask holding 1.</returns>
/// <example><c>Get().Result</c> returns 1.</example>
public static ValueTask<int> Get() => ValueTask.FromResult(1);
}
0b1010 and 1_000_000.
public static class V70Literals
{
/// <summary>Adds a binary literal to a separated decimal.</summary>
/// <returns>1000005.</returns>
/// <example><c>Demo()</c> returns 1000005.</example>
public static int Demo() => 1_000_000 + 0b0101; // 0b0101 is 5
}
field, ?. on the left of =, nameof(List<>), first-class spans, modifiers on untyped lambda parameters, partial constructors and events, in-place compound operators.params spans, the Lock type, allows ref struct, partial properties.required, generic math.init, top-level statements, record structs, global usings, file-scoped namespaces.Span support with ref struct and in, switch expressions, nullable references, async streams, ranges.That is the last page. Go back to the C# interview home to pick a pattern or a guide to review.