Below are my personal guidelines for when I write C# code, or code in general really. It started as collaboration with a friend many moons ago, just so we'd have something to lean on when discussing pull requests to a project we were working on together. The guidelines were originally based on the .NET Runtime coding guidelines, anno 2018 or so, but has since been expanded to this monstrosity of definition and excruciating detail. It is formulated as if it were written by a group of people, since it is intended to be used in a project as either the basis or starting point for an official coding guidelines document. It could even be used as a provocation just to start a conversation about code and its quialities.
If you read this, and actually make it to the end, I hope to leave you either having confronted some of your own concepts of what makes code "good" or reinforced what you're already doing. Adding some context and factors that shores up the "why" in what do "this way or that".
Or failing that at least gave you fuel for your own righteous crusade and a break from our burning world at large.
PS. If you feel like using this, please include some attribution or a link to the original, unadulterated, version. Not for my sake. But so that others, especially if included/published in a public setting, can see where this wellspring of complete and absolute correctness originated. It is my intention to keep all versions going forward available on this site - somehow. We'll see. - - - Cheers!
This document outlines a set of guidelines regarding coding and coding style for C#. The guidelines are written in first person plural (we) to signal that this document is consensus driven and not dogma passed from up on high. It is a living document that is supposed to change with the times.
The collective preferences expressed in this document are defined and maintained by the suggestions of individual developers. Suggestions are to be discussed in the group, and when reaching consensus the suggestions are to be realized as additions to, alterations in, or removals from this document.
The guiding principles for this document are sustainability and stability. In this context sustainability refers to our aspiration to make it easier to write, and by extension read, code the way we prefer, even if the developer is less experienced or is coming from a different way of working. Stability means that our code is stable as written between machines and development environments. The settings of a given editor or the like should not impact how the code is presented. Some things may be enforced with .editorconfig files, others rely on the developers following the guidelines of this document. The end goal of these principles is to maintain readability and as far as possible keep our code self documenting.
There are certain words that have different meanings when used in everyday speech depending on context. Context, intonation, and body language inform the intended meaning of the word as it's used. This is all but lost in text. To mitigate misunderstandings this chapter contains a list inspired in form by RFC2119. When the key words in the list are used as written, in all caps, they mean only what they are listed as meaning below. When not written in all caps, the key words are intended to have their respective colloquial meaning in the given context.
| Keyword | Meaning |
|---|---|
| MUST REQUIRED SHALL |
These words mean that the definition is an absolute requirement. |
| MUST NOT SHALL NOT |
These phrases mean that the definition is an absolute prohibition. |
| SHOULD RECOMMENDED |
These words mean that the definition is the preferred one. There may exist valid reasons in certain circumstances to ignore the definition. Any deviations must be motivated by increased readability, maintainability, or similar reasoning. |
| SHOULD NOT NOT RECOMMENDED |
These phrases mean that the definition is undesirable. There may exist valid reasons in certain circumstances to ignore the definition. Any deviations must be motivated by increased readability, maintainability, or similar reasoning. |
| MAY OPTIONAL |
These words mean that a definition is truly optional. |
| PREFER | This word means that the definition is the preferred one. There may exist valid reasons in certain circumstances to ignore the definition. Any deviations must be motivated by increased readability, maintainability, or similar reasoning. This word is considered a stronger proposition than SHOULD/RECOMMENDED and the full implications must be understood and carefully weighed before choosing a different course. |
| AVOID | This word means that the definition is undesirable. There may exist valid reasons in certain circumstances to ignore the definition. Any deviations must be motivated by increased readability, maintainability, or similar reasoning. This word is considered a stronger proposition than SHOULD NOT/NOT RECOMMENDED and the full implications must be understood and carefully weighed before choosing a different course. |
using statement is permitted to be nested within another using statement by starting on
the following line at the same indentation level, even if the nested using contains a control block.
if/else/if else statement requires more than one line, then all other
statements in that chain must also have braces.
else and else if constructs. This does not include guard clauses and the
like.
namespace declarations.
System.* namespaces, which are to
be placed on top of all others.
using statements.int, string, float instead of
Int32, String, Single, etc) for both type references as well as method calls (e.g. int.TryParse()
instead of Int32.TryParse()).
var over explicit types when the type is reasonably deducible.null propagation over explicit
null checks and ternary operator use.
is null and is not null over ==, !=, and
object.ReferenceEquals() for null checks.
as-with-null check or
is-with-cast, over repeated boolean expressons and mixed type checking.
PascalCase for properties and methods, including local functions.camelCase for local variables and method arguments._camelCase for fields.readonly where possible for fields. When used on static fields,
readonly SHALL come after static (e.g. static readonly not
readonly static).
private fields. public access SHOULD be handled via properties or methods.
private string _member and not string _member;, or internal class Class and
not class Class.
public abstract and not
abstract public.
const unless absolutely necessary.public const.this. unless absolutely necessary.nameof() over string literals, when applicable.goto.internal and private types static or
sealed unless derivation is required.
static classes.static.private or
protected.
#region because it only really serves to make the code harder to read at a glance.
string.Format() and string concatenation using the
+ operator.
public interface ConceptualThing over public interface IConceptualThing.
InvalidOperation over InvalidOperationException, or
NullDereferenced over NullReferenceException. See Kevlin Henney's talk at
Istanbul Tech Talks 2016
at 28:50 or this tweet for motivation as to
why.
partial classes.public setters. An exception can be DTOs, but it's still advisable to have them be
immutable.
System.HashCode when overriding Object.GetHashCode().// Ok
if (contidion)
DoThis();
else
DoThat();
if (condition)
{
DoThis();
}
else
{
DoThat();
ThenThis();
}
// Not ok
if (condition)
{
DoThis();
ThenThis();
}
else
DoThat();
else// Ok
if (condition)
throw new Exception();
if (condition)
{
return valueOne;
}
else if (condition)
{
return valueTwo;
}
else
{
var valueThree = Operation();
return valueThree;
}
// Not ok
if (condition)
return valueOne;
if (condition)
return valueTwo;
var valueThree = Operation();
return valueThree;
// Prefer
public class Class
{
public int Integer { get; init; }
}
public class Class
{
public int Integer { get; private set; }
public string Text { get; }
public Class(string text, /* ... */)
{
Text = text;
DoThis(/* ... */);
}
public void DoThis(/* ... */)
{
/* ... */
Integer = value;
}
}
// Over
public class Class
{
public int Integer { get; set; }
public string Text { get; set; }
}
// Prefer
public class EMail : ValueObject<EMail>
{
public string Recipient { get; }
public string Domain { get; }
public string Address => Recipient + "@" + Domain;
public EMail(string email)
{
/* ... */
}
}
public class EMailRecipient
{
public EMail Address { get; }
}
// Over
public class EMailRecipient
{
public string Address { get; }
}
// Prefer
var b = DoThis() && DoThat();
// Over
var b = DoThis() && DoThat() ? true : false;
// Prefer
void DoThis(int arg0, int arg1, string arg2);
void DoThat(
int arg0,
int arg1,
string arg2,
Collection<string> arg3,
Collection<int> arg4);
// Over
void DoThis(int arg0, int arg1, string arg2);
void DoThat(int arg0, int arg1, string arg2,
Collection<string> arg3, Collection<int> arg4);
void DoOther(int arg0, int arg1, string arg2, Collection<string> arg3, Collection<int> arg4);
// Prefer
var foo =
collection
.MethodA(/* ... */)
.MethodB(/* ... */);
var bar = serviceInstance
.MethodA(/* ... */)
.Property;
return
instanceObject
.Property
.MethodA(/* ... */);
// Prefer
var e = array[^1];
var s = text[2..^4];
// Over
var e = array[array.Length - 1];
var s = text.Substring(2, text.Length - 4);
// Prefer
return n is 1 or 2;
if (x is int i) /* ... */
if (x is not int i) /* ... */
if (y as string s) /* ... */
// Over
return n == 1 || n == 2;
if (x is int)
{
var i = (int)x;
/* ... */
}
if (!(x is int i)) /* ... */
var s = y as string;
if (s is not null) /* ... */
// Prefer
if (int.TryParse(value, out var i)) /* ... */
var (name, age) = PersonAsTuple();
Console.WriteLine($"{name} is {age} years old.");
(int x, int y) = StartingPointAsTuple();
Console.WriteLine($"Starting point is {x} by {y}.");
// Over
int i;
if (int.TryParse(value, out i)) /* ... */
var person = PersonAsTuple();
Console.WriteLine($"{person.name} is {person.age} years old.");
(int x, int y) point = StartingPointAsTuple();
Console.WriteLine($"Starting point is {point.x} by {point.y}.");
null checking// Prefer
func?.Invoke(args);
var z = x ?? y;
var s = o?.ToString();
if (x is null)
return;
// NB: Prior to C# 9.0/.NET 6 the `not` key word is not available. Use
// `!(y is null)` instead.
if (y is not null)
DoThis();
// Over
if (func != null)
func(args);
var z = x != null ? x : y;
var z = x == null ? y : x;
var s = o != null ? o.ToString() : null;
var s = o == null ? null : o.ToString();
if (x == null)
return;
if (object.ReferenceEquals(x, null))
return;
if ((object)y != null)
DoThis();
// Prefer
public override int GetHashCode()
{
return System.HashCode.Combine(
PropertyA,
PropertyB,
PropertyC,
PropertyD);
}
// Over
public override int GetHashCode()
{
var hashCode = 336910899;
hashCode = hashCode * -1521134295 + PropertyA.GetHashCode();
hashCode = hashCode * -1521134295 + PropertyB.GetHashCode();
hashCode = hashCode * -1521134295 + PropertyC.GetHashCode();
hashCode = hashCode * -1521134295 + PropertyD.GetHashCode();
return hashCode;
}
// Prefer
var foo = context
.Entity
.Where(e => e.Property == value)
.Select(e => e.OtherProperty);
// Over
var foo =
from e in context.Entities
where e.Property == value
select e.OtherProperty;
© Tech Tree AB 2026