Framework Guidelines

Edit this page

Prefer idiomatic C# over .NET Framework APIs (AV2202)

C#’s language syntax makes code more concise. The abstractions make later refactorings easier (and sometimes allow for extra optimizations).

Prefer:

(string, int) tuple = ("", 1);

rather than:

ValueTuple<string, int> tuple = new ValueTuple<string, int>("", 1);

Prefer:

DateTime? startDate;

rather than:

Nullable<DateTime> startDate;

Prefer:

if (startDate is null) ...

rather than:

if (startDate == null) ...

Prefer:

if (startDate is not null) ...

rather than:

if (startDate.HasValue) ...

Prefer:

if (startDate > DateTime.Now) ...

rather than:

if (startDate.HasValue && startDate.Value > DateTime.Now) ...

Prefer:

List<string> items = [];

rather than:

List<string> items = new List<string>();

Prefer (C# 14):

list ??= [];

rather than:

if (list == null) list = [];

Build with the highest warning level (AV2210)

Configure the development environment to use the highest available warning level for the C# compiler, and enable the option Treat warnings as errors. This allows the compiler to enforce the highest possible code quality.

Avoid LINQ query syntax for simple expressions (AV2220)

Rather than:

var query = from item in items where item.Length > 0 select item;

prefer the use of extension methods from the System.Linq namespace:

var query = items.Where(item => item.Length > 0);

The second example is a bit less convoluted.

Use deconstruction to simplify variable assignments (AV2225)

C# supports deconstructing tuples, records, and any type that defines a Deconstruct method. Use this to avoid intermediate variables and make the intent of your code clearer.

Instead of:

public record Point(int X, int Y);

Point point = GetOrigin();
int x = point.X;
int y = point.Y;

write:

(int x, int y) = GetOrigin();

Similarly, deconstruct arrays and collections with pattern matching to avoid index-based access:

if (items is [int first, int second, ..])
{
	// use first and second directly
}

[!TIP] Deconstruction also works in foreach loops:

foreach ((int key, int value) in dictionary)
{
	Console.WriteLine($"{key}: {value}");
}

Only use the dynamic keyword when talking to a dynamic object (AV2230)

The dynamic keyword has been introduced for interop with languages where properties and methods can appear and disappear at runtime. Using it can introduce a serious performance bottleneck, because various compile-time checks (such as overload resolution) need to happen at runtime, again and again on each invocation. You’ll get better performance using cached reflection lookups, Activator.CreateInstance() or pre-compiled expressions (see here for examples and benchmark results).

While using dynamic may improve code readability, try to avoid it in library code (especially in hot code paths). However, keep things in perspective: we’re talking microseconds here, so perhaps you’ll gain more by optimizing your SQL statements first.