c Sharp Coding Guidelines
c Sharp Coding Guidelines
In general, if I have a discussion with a colleague about a smell that this document does not cover, I'll refer back to a set of
basic principles that apply to all situations, regardless of context. These include:
The Principle of Least Surprise (or Astonishment): you should choose a solution that everyone can understand, and that
keeps them on the right track.
Keep It Simple Stupid (a.k.a. KISS): the simplest solution is more than sufficient.
You Ain't Gonna Need It (a.k.a. YAGNI): create a solution for the problem at hand, not for the ones you think may
happen later on. Can you predict the future?
Don't Repeat Yourself (a.k.a. DRY): avoid duplication within a component, a source control repository or a bounded
context, without forgetting the Rule of Three heuristic.
The four principles of object-oriented programming: encapsulation, abstraction, inheritance and polymorphism.
In general, generated code should not need to comply with coding guidelines. However, if it is possible to modify the
templates used for generation, try to make them generate code that complies as much as possible.
Regardless of the elegance of someone's solution, if it's too complex for the ordinary developer, exposes unusual behavior,
or tries to solve many possible future issues, it is very likely the wrong solution and needs redesign. The worst response a
developer can give you to these principles is: "But it works?".
Additionally, after reading Robert C. Martin's book Clean Code: A Handbook of Agile Software Craftsmanship, I became a big
fan of his ideas and decided to include some of his smells and heuristics as guidelines. Notice though, that this document is
in no way a replacement for his book. I sincerely recommend that you read his book to gain a solid understanding of the
rationale behind his recommendations.
I've also decided to include some design guidelines in addition to simple coding guidelines. They are too important to ignore
and have a big influence in reaching high quality code.
To help you in this decision, I've assigned a level of importance to each guideline:
Guidelines that you should never skip and should be applicable to all situations
Notice though that it merely reflects my view on proper C# code so Aviva Solutions will not be liable for any direct or indirect
damages caused by applying the guidelines of this document. This document is published under a Creative Commons
license, specifically the Creative Commons Attribution-ShareAlike 4.0 license.
Tip: A class with the word And in it is an obvious violation of this rule.
Tip: Use Design Patterns to communicate the intent of a class. If you can't assign a single design pattern to a class, chances
are that it is doing more than one thing.
Note If you create a class representing a primitive type you can greatly simplify its use by making it immutable.
Use an interface rather than a base class to support multiple implementations (AV1004)
If you want to expose an extension point from your class, expose it as an interface rather than as a base class. You don't want
to force users of that extension point to derive their implementations from a base class that might have an undesired
behavior. However, for their convenience you may implement a(n abstract) default implementation that can serve as a
starting point.
Note: If you really need that static class, mark it as static so that the compiler can prevent instance members and
instantiating your class. This relieves you of creating an explicit private constructor.
Don't suppress compiler warnings using the new keyword (AV1010)
Compiler warning CS0114 is issued when breaking Polymorphism, one of the most essential object-orientation principles.
The warning goes away when you add the new keyword, but it keeps sub-classes difficult to understand. Consider the
following two classes:
This will cause behavior that you would not normally expect from class hierarchies:
It should not make a difference whether you call Print() through a reference to the base class or through the derived class.
Note: This rule is also known as the Liskov Substitution Principle, one of the S.O.L.I.D. principles.
someObject.SomeProperty.GetChild().Foo()
An object should not expose any other classes it depends on because callers may misuse that exposed property or method
to access the object behind it. By doing so, you allow calling code to become coupled to the class you are using, and thereby
limiting the chance that you can easily replace it in a future stage.
Note: Using a class that is designed using the Fluent Interface pattern seems to violate this rule, but it is simply returning
itself so that method chaining is allowed.
Exception: Inversion of Control or Dependency Injection frameworks often require you to expose a dependency as a public
property. As long as this property is not used for anything other than dependency injection I would not consider it a
violation.
Exception: Domain models such as defined in Domain-Driven Design tend to occasionally involve bidirectional associations
that model real-life associations. In those cases, make sure they are really necessary, and if they are, keep them in.
Exception: The only exceptions to this rule are classes that are used to transfer data over a communication channel, also
called Data Transfer Objects, or a class that wraps several parameters of a method.
this.age = years;
}
// ...
}
Member Design Guidelines
Allow properties to be set in any order (AV1100)
Properties should be stateless with respect to other properties, i.e. there should not be a difference between first setting
property DataSource and then DataMember or vice-versa.
This violation is often seen in domain models and introduces all kinds of conditional logic related to those conflicting rules,
causing a ripple effect that significantly increases the maintenance burden.
A classic example of this is the HttpContext.Current property, part of ASP.NET. Many see the HttpContext class as a source
of a lot of ugly code.
Properties, arguments and return values representing strings, collections or tasks should never be
null (AV1135)
Returning null can be unexpected by the caller. Always return an empty collection or an empty string instead of a null
reference. When your member returns Task or Task<T> , return Task.CompletedTask or Task.FromResult() . This also
prevents cluttering your code base with additional checks for null , or even worse, string.IsNullOrEmpty() .
Note: An easy trick to remember this guideline is the Don't ship the truck if you only need a package.
Exceptions that occur within an async / await block and inside a Task 's action are propagated to the awaiter.
Exceptions that occur in the code preceding the asynchronous block are propagated to the caller.
Note: Derived classes that override the protected virtual method are not required to call the base class implementation. The
base class must continue to work correctly even if its implementation is not called.
Don't pass null as the sender argument when raising an event (AV1235)
Often an event handler is used to handle similar events from multiple senders. The sender argument is then used to get to
the source of the event. Always pass a reference to the source (typically this ) when raising the event. Furthermore don't
pass null as the event data parameter when raising an event. If there is no event data, pass EventArgs.Empty instead of
null .
class SomeClass
{
}
// Don't
class MyClass
{
void SomeMethod(T t)
{
object temp = t;
SomeClass obj = (SomeClass) temp;
}
}
// Do
class MyClass where T : SomeClass
{
void SomeMethod(T t)
{
SomeClass obj = t;
}
}
var query =
from customer in db.Customers
where customer.Balance > GoldMemberThresholdInEuro
select new GoldMember(customer.Name, customer.Balance);
return query;
}
Since LINQ queries use deferred execution, returning query will actually return the expression tree representing the above
query. Each time the caller evaluates this result using a foreach loop or similar, the entire query is re-executed resulting in
new instances of GoldMember every time. Consequently, you cannot use the == operator to compare multiple GoldMember
instances. Instead, always explicitly evaluate the result of a LINQ query using ToList() , ToArray() or similar methods.
Do not use this and base prefixes unless it is required (AV1251)
In a class hierarchy, it is not necessary to know at which level a member is declared to use it. Refactoring derived classes is
harder if that level is fixed in the code.
Maintainability Guidelines
Methods should not exceed 7 statements (AV1500)
A method that requires more than 7 statements is simply doing too much or has too many responsibilities. It also requires
the human mind to analyze the exact statements to understand what the code is doing. Break it down into multiple small
and focused methods with self-explaining names, but make sure the high-level algorithm is still clear.
Make all members private and types internal sealed by default (AV1501)
To make a more conscious decision on which members to make available to other classes, first restrict the scope as much as
possible. Then carefully decide what to expose as a public member or type.
Double negatives are more difficult to grasp than simple expressions, and people tend to read over the double negative
easily.
As an example, consider a group of classes organized under the namespace AvivaSolutions.Web.Binding exposed by a
certain assembly. According to this guideline, that assembly should be called AvivaSolutions.Web.Binding.dll .
Exception: If you decide to combine classes from multiple unrelated namespaces into one assembly, consider suffixing the
assembly name with Core , but do not use that suffix in the namespaces. For instance, AvivaSolutions.Consulting.Core.dll .
Exception: Types that only differ by their number of generic type parameters should be part of the same file.
Name a source file to the logical function of the partial type (AV1508)
When using partial types and allocating a part per file, name each file after the logical part that part plays. For example:
// In MyClass.cs
public partial class MyClass
{...}
// In MyClass.Designer.cs
public partial class MyClass
{...}
Instead, do this:
using System.Collections.Generic;
If you do need to prevent name clashing, use a using directive to assign an alias:
Strings intended for logging or tracing are exempt from this rule. Literals are allowed when their meaning is clear from the
context, and not subject to future changes, For example:
mean = (a + b) / 2; // okay
WaitMilliseconds(waitTimeInSeconds * 1000); // clear enough
If the value of one constant depends on the value of another, attempt to make this explicit in the code.
Note: An enumeration can often be used for certain types of symbolic constants.
// Built-in types.
bool isValid = true;
string phoneNumber = "(unavailable)";
uint pageSize = Math.Max(itemCount, MaxPageSize);
Exception: Multiple assignments per statement are allowed by using out variables, is-patterns or deconstruction into tuples.
Examples:
Always add a block after the keywords if, else, do, while, for, foreach and case (AV1535)
Please note that this also avoids possible confusion in statements of the form:
if (isActive) if (isVisible) Foo(); else Bar(); // which 'if' goes with the 'else'?
Always add a default block after the last case in a switch statement (AV1536)
Add a descriptive comment if the default block is supposed to be empty. Moreover, if that block is not supposed to be
reached throw an InvalidOperationException to detect future changes that may fall through the existing cases. This ensures
better code, because all paths the code can travel have been thought about.
case "yes":
{
Console.WriteLine("You answered with Yes");
break;
}
default:
{
// Not supposed to end up here.
throw new InvalidOperationException("Unexpected answer " + answer);
}
}
}
bool isPositive;
if (value > 0)
{
isPositive = true;
}
else
{
isPositive = false;
}
write:
bool isPositive = value > 0;
Or instead of:
string classification;
if (value > 0)
{
classification = "positive";
}
else
{
classification = "negative";
}
return classification;
write:
Or instead of:
int result;
if (offset == null)
{
result = -1;
}
else
{
result = offset.Value;
}
return result;
write:
Or instead of:
write:
if (employee.Manager != null)
{
return employee.Manager.Name;
}
else
{
return null;
}
write:
return employee.Manager?.Name;
// GOOD
string result = $"Welcome, {firstName} {lastName}!";
// BAD
string result = string.Format("Welcome, {0} {1}!", firstName, lastName);
// BAD
string result = "Welcome, " + firstName + " " + lastName + "!";
// BAD
string result = string.Concat("Welcome, ", firstName, " ", lastName, "!");
In order to understand what this expression is about, you need to analyze its exact details and all of its possible outcomes.
Obviously, you can add an explanatory comment on top of it, but it is much better to replace this complex expression with a
clearly named method:
if (NonConstructorMemberUsesNewKeyword(member))
{
// do something
}
You still need to understand the expression if you are modifying it, but the calling code is now much easier to grasp.
The class MyString provides three overloads for the IndexOf method, but two of them simply call the one with one more
parameter. Notice that the same rule applies to class constructors; implement the most complete overload and call that one
from the other overloads using the this() operator. Also notice that the parameters with the same name should appear in
the same position in all overloads.
Important: If you also want to allow derived classes to override these methods, define the most complete overload as a non-
private virtual method that is called by all overloads.
public virtual int IndexOf(string phrase, int startIndex = 0, int count = -1)
{
int length = count == -1 ? someText.Length - startIndex : count;
return someText.IndexOf(phrase, startIndex, length);
}
Since strings, collections and tasks should never be null according to rule AV1135, if you have an optional parameter of
these types with default value null then you must use overloaded methods instead.
Strings, unlike other reference types, can have non-null default values. So an optional string parameter may be used to
replace overloads with the condition of having a non-null default value.
1) The default values of the optional parameters are stored at the caller side. As such, changing the default argument
without recompiling the calling code will not apply the new default value. Unless your method is private or internal, this
aspect should be carefully considered before choosing optional parameters over method overloads.
2) If optional parameters cause the method to follow and/or exit from alternative paths, overloaded methods are probably a
better fit for your case.
Do not use optional parameters in interface methods or their concrete implementations (AV1554)
When an interface method defines an optional parameter, its default value is discarded during overload resolution unless
you call the concrete class through the interface reference.
When a concrete implementation of an interface method sets a default argument for a parameter, the default value is
discarded during overload resolution if you call the concrete class through the interface reference.
See the series on optional argument corner cases by Eric Lippert (part one, two, three, four) for more details.
Exception: The only exception where named arguments improve readability is when calling a method of some code base you
don't control that has a bool parameter, like this:
If you want to use more parameters, use a structure or class to pass multiple arguments, as explained in the Specification
design pattern. In general, the fewer the parameters, the easier it is to understand the method. Additionally, unit testing a
method with many parameters requires many scenarios to test.
Exception: Calling and declaring members that implement the TryParse pattern is allowed. For example:
On first sight this signature seems perfectly fine, but when calling this method you will lose this purpose completely:
Often, a method taking such a bool is doing more than one thing and needs to be refactored into two or more methods. An
alternative solution is to replace the bool with an enumeration.
write:
requires extra steps to inspect intermediate method return values. On the other hard, were this expression broken into
intermediate variables, setting a breakpoint on one of them would be sufficient.
Note This does not apply to chaining method calls, which is a common pattern in fluent APIs.
Naming Guidelines
Use US English (AV1701)
All identifiers (such as types, type members, parameters and variables) should be named using words from the American
English language.
Choose easily readable, preferably grammatically correct names. For example, HorizontalAlignment is more readable
than AlignmentHorizontal .
Favor readability over brevity. The property name CanScrollHorizontally is better than ScrollableX (an obscure
reference to the X-axis).
Avoid using names that conflict with keywords of widely used programming languages.
Language
Casing Example
element
Type
Pascal TView
parameter
Class,
Pascal AppDomain
struct
Enum
Pascal FatalError
member
Resource
Pascal SaveButtonTooltipText
key
Constant
Pascal MaximumItems
field
Private
static
Pascal RedValue
readonly
field
Private
Camel listItem
field
Non-
private Pascal MainPanel
field
Local
Pascal FormatText
function
Variables
declared (string first, string last) = ("John", "Doe");
Camel
using tuple var (first, last) = ("John", "Doe");
syntax
Local
Camel listOfValues
variable
Exceptions: Use well-known acronyms and abbreviations that are widely accepted or well-known in your work domain. For
instance, use acronym UI instead of UserInterface and abbreviation Id instead of Identity .
Name members, parameters and variables according to their meaning and not their type (AV1707)
Use functional names. For example, GetLength is a better name than GetInt .
Don't use terms like Enum , Class or Struct in a name.
Identifiers that refer to a collection type should have plural names.
Don't include the type in variable names, except to avoid clashes with other variables.
Don't include terms like Utility or Helper in classes. Classes with names like that are usually static classes and are
introduced without considering object-oriented principles (see also AV1008).
Name generic type parameters with descriptive names (AV1709)
class Employee
{
// Wrong!
static GetEmployee() {...}
DeleteEmployee() {...}
// Right.
static Get() {...}
Delete() {...}
// Also correct.
AddNewJob() {...}
RegisterForMeeting() {...}
}
Avoid short names or names that can be mistaken for other names (AV1712)
Although technically correct, statements like the following can be confusing:
Name methods and local functions using verbs or verb-object pairs (AV1720)
Name a method or local function using a verb like Show or a verb-object pair such as ShowDialog . A good name should give a
hint on the what of a member, and if possible, the why.
Also, don't include And in the name of a method or local function. That implies that it is doing more than one thing, which
violates the Single Responsibility Principle explained in AV1115.
Note: Never allow namespaces to contain the name of a type, but a noun in its plural form (e.g. Collections ) is usually OK.
Suppose you want to define events related to the deletion of an object. Avoid defining the Deleting and Deleted events as
BeginDelete and EndDelete . Define those events as follows:
Note If using C# 9 or higher, use a single underscore (discard) for all unused lambda parameters and variables.
Note: If you return an IEnumerable<T> to prevent changes from calling code as explained in AV1130, and you're developing
in .NET 4.5 or higher, consider the new read-only classes.
You will likely end up with a deadlock. Why? Because the Result property getter will block until the async operation has
completed, but since an async method could automatically marshal the result back to the original thread (depending on the
current SynchronizationContext or TaskScheduler ) and WPF uses a single-threaded synchronization context, they'll be
waiting on each other. A similar problem can also happen on UWP, WinForms, classical ASP.NET (not ASP.NET Core) or a
Windows Store C#/XAML app. Read more about this here.
// OK / GOOD
int bytesRead = await stream.ReadAsync(buffer, cancellationToken);
// OK / GOOD
int bytesRead = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
// OK / GOOD - Get task if you want to overcome the limitations exposed by ValueTask / ValueTask<T>
Task<int> task = stream.ReadAsync(buffer, cancellationToken).AsTask();
Other usage patterns might still work (like saving the ValueTask / ValueTask<T> into a variable and awaiting later), but may
lead to misuse eventually. Not awaiting a ValueTask / ValueTask<T> may also cause unwanted side-effects. Read more about
ValueTask / ValueTask<T> and the correct usage here.
Framework Guidelines
Use C# type aliases instead of the types from the System namespace (AV2201)
For instance, use object instead of Object , string instead of String , and int instead of Int32 . These aliases have been
introduced to make the primitive types first class citizens of the C# language, so use them accordingly. When referring to
static members of those types, use int.Parse() instead of Int32.Parse() .
Exception: For interop with other languages, it is custom to use the CLS-compliant name in type and member signatures, e.g.
HexToInt32Converter , GetUInt16 .
Prefer:
rather than:
Prefer:
DateTime? startDate;
rather than:
Nullable<DateTime> startDate;
Prefer:
rather than:
if (startDate.HasValue) ...
Prefer:
rather than:
Prefer:
rather than:
if (tuple1.startTime == tuple2.startTime && tuple1.duration == tuple2.duration) ...
var query = from item in items where item.Length > 0 select item;
Or even better:
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.
Tip: Even if you need to target .NET Framework 4.0 you can use the async and await keywords. Simply install the Async
Targeting Pack.
Documentation Guidelines
Write comments and documentation in US English (AV2301)
Document all public, protected and internal types and members (AV2305)
Documenting your code allows Visual Studio, Visual Studio Code or Jetbrains Rider to pop-up the documentation when your
class is used somewhere else. Furthermore, by properly documenting your classes, tools can generate professionally looking
class documentation.
Keep one space between keywords like if and the expression, but don't add spaces after ( and before ) such as:
if (condition == null) .
Don't indent object/collection initializers and initialize each property on a new line, so use a format like this:
Don't indent lambda statement blocks and use a format like this:
methodThatTakesAnAction.Do(source =>
{
// do something like this
}
Keep expression-bodied-members on one line. Break long lines after the arrow sign, like this:
Put the entire LINQ statement on one line, or start each keyword at the same indentation, like this:
var query = from product in products where product.Price > 10 select product;
or
var query =
from product in products
where product.Price > 10
select product;
Start the LINQ statement with all the from expressions and don't interweave them with restrictions.
Remove redundant parentheses in expressions if they do not clarify precedence. Add parentheses in expressions to
avoid non-obvious precedence. For example, in nested conditional expressions: overruled || (enabled && active) ,
bitwise and shift operations: foo | (bar >> size) .
Add an empty line between multi-line statements, between multi-line members, after the closing curly braces, between
unrelated code blocks, and between the using statements of different root namespaces.
Using static directives and using alias directives should be written below regular using directives. Always place these
directives at the top of the file, before any namespace declarations (not inside them).
Declare local functions at the bottom of their containing method bodies (after all executable code).
Code Complete: A Practical Handbook of Software Construction (Steve McConnel) One of the best books I've ever read.
It deals with all aspects of software development, and even though the book was originally written in 2004 you'll be
surprised when you see how accurate it still is. I wrote a review in 2009 if you want to get a sense of its contents.
The Art of Agile Development (James Shore) Another great all-encompassing trip through the many practices preached
by processes like Scrum and Extreme Programming. If you're looking for a quick introduction with a pragmatic touch,
make sure you read James's book.
Applying Domain-Driven Design and Patterns: With Examples in C# and .NET (Jimmy Nilsson) The book that started my
interest for both Domain-Driven Design and Test-Driven Development. It's one of those books that I wished I had read a
few years earlier. It would have spared me from many mistakes.
Jeremy D. Miller's Blog Jeremy has written some excellent blog posts on Test-Driven Development, Design Patterns and
design principles. I've learned a lot from his real-life and practical insights.
LINQ Framework Design Guidelines A set of rules and recommendations that you should adhere to when creating your
own implementations of IQueryable .
Guidance on Asynchronous Programming (David Fowler) Best practices for async / await with examples of bad and good
patterns of how to write asynchronous code.
Best Practices for c# async/await Older (but still valid) overview of crucial practices to follow when adopting async and
await in your own code base.