Messerli C# Coding Guidelines
These are the revised Coding Guidelines valid for code newly written in C#. If you see mistakes or have, comments do not hesitate in contacting the developer council.
When and where are these rules applicable
These rules are applicable to all C# code written at Messerli Informatik AG. The rules will not be enforced for old code, which has not been changed. In a review, these coding guidelines should be checked for all code, which has changed. Changes necessary due to the coding guidelines should be in a reasonable ratio to the functional changes.
Update to the rules
Updates to the rules should be sent to the developer council. The developer council will integrate new rules in a timely fashion when the council approves and will give feedback otherwise.
Definition of done
- Feature is tested against acceptance criteria
- Code adheres to the Coding Guidelines
- Unit tests pass
- Code is unit tested
- Code is reviewed
Physical structure
Each class should go into its own file with the same name
Only one class should be in a file. This also applies to enumeration types (enum
).
Inner classes are allowed but discouraged.
Each namespace should go into its own folder with the same name
All classes that are held together by a common purpose should go into its own namespace; the namespace should be reflected in the folder structure. If you have a namespace Example, it should be in a folder example. Set a correct default namespace in the project, this way the tooling will help you to move classes into the correct location.
Each project should have a test project
We write tests in a separate project, therefore each project needs a test-project.
Formatting rules
We use Visual Studio, ReSharper and CodeMaid to enforce most of the formatting rules.
Tool configurations
The Formatting Rules for Visual Studio, ReSharper and CodeMaid are checked into the Repository, and can be found in the CTO git-Repository in the folder Tool Configurations:
- .editorconfig
- CodeMaid.config
- MesserliResharper.DotSettings
- VS_Format_Settings.vssettings
Indentation
For each scope that is opened, we indent by one level. For indentation, we use only spaces, tabs are forbidden. One level of indentation is four spaces.
Maximum line length
There is no absolute maximum to the length of the line, but try to keep it on the screen.
Empty lines
We use only one empty line to separate content.
Naming Conventions
Names should be descriptive; avoid abbreviations. Give a descriptive name, but be specific. Do not worry about saving horizontal space, as it is far more important to make your code immediately understandable by a new reader. We do not contract words or make up words.
We use English words for abstractions
- The programming language is in English, most concepts are in English, therefore English is a lot easier to make consistent. (WriteExportAbacusEinFile, etc.)
- Most of the concepts except a few in the building industry are already in English.
- We want to avoid being confused about GetProject vs GetProjekt. For a single concept, only one word in one language should be chosen.
- Avoid variables like workWork (sic)
- It is a lot easier to create plurals in English (-s, -ies) where in German it might be difficult: Kapitel, Mitarbeiter, Unternehmer, Fenster, Artikel (see also next point)
- Proper Nouns should not be translated even if possible.
- We try to use the same word for the same abstraction. It is either a project or a tenant. (DDD)
Be specific
Only use generic words like, data, list, string, number, manager, gateway and handler if necessary in a generic context. Otherwise, try to find specific domain words. Build a domain specific ubiquitous language. In domain context, always use the domain vocabulary and try keeping the overhead to a minimum, be precise.
Abbrevations
We do not use abbreviations that are ambiguous or unfamiliar to readers outside our project, and we do not abbreviate by skipping letters within a word. Abbreviations that would be familiar to someone outside your project with relevant domain knowledge are OK. As a rule of thumb, an abbreviation is probably OK if it is listed in Wikipedia like IP or HTML. All abbreviations are written in PascalCase.
class JsonToHtmlConverter
{
}
class IpAddress
{
}
class UserId
{
}
class TfsConnector
{
}
Abbrevation Examples
Use plural and singular to your advantage
If you have a collection of things, use the plural form of the variable you would use for a single element. Prefer being specific, as the name of the collection should reflect the meaning behind its elements. This gives you a natural understanding on what object you are dealing with.
var onlineUsers = GetOnlineUsers();
for (var user in onlineUsers) {
user.GoOffline();
}
Correct usage of plural
Try to use the same word for the same concept
Do not switch between different name for a concept, make a choice and stick to it.
Naming the different C# identifiers
Table following table gives you the rules for each identifier.
Identifier | Casing | Prefix / Suffix | Example |
---|---|---|---|
Namespace | PascalCase | Messerli.Core | |
Class | PascalCase | WarpEngine | |
Exception class | PascalCase | <Name>Exception | InvalidArgumentException |
Interface | PascalCase | I<Name> | ISyntaxTree |
Abstract class | PascalCase | Application | |
Method | PascalCase | DrawSquare | |
Properties | PascalCase | FirstName | |
Predicate method | PascalCase | Is<Name>, Has<Name>, Are<Name>, Have<Name> | IsGreat, HasField |
Public Member Variable | PascalCase | Diameter | |
Protected member variable | camelCase | _<name> | _tableIndex |
Private member variable | camelCase | _<name> | _adjacencyMatrix |
Local variable | camelCase | index, name, helpLabel | |
Global constant | PascalCase | Pi, PrimeNumbers | |
Class constant | PascalCase | FilePath | |
Enum type | PascalCase | StatusType | |
Enum value | PascalCase | RequiredValue | |
Lambda Parametrs | camelCase or lower-case letter | cornerPoint, c, name, n |
UI Elements
When dealing with UI Elements like buttons, combo boxes, grids, text boxes, etc. we append the full name of the type without any prefix to the variable name.
class UserRightDialog
{
Grid _userGrid;
Edit _userNameEdit;
Button _okButton;
};
Naming of UI Elements
Naming of delegate variables
All delegate variables should be in camelCase without a prefix or postfix. All other Methods are PascalCase which means if you see a createThing(), you will know that it is indeed a delegate call.
Naming of Tests
The naming of tests must describe what the intended effect of the methods that are being tested, e.g. ReturnsNullOnEmptySettings
or ThrowsOnInvalidResponse
.
Variables
Only one declaration per line
Multiple declarations per line are not allowed. This way, we can reduce the mental baggage of the reader and avoid awkward pointer and reference declarations (See the next subchapter). Deconstruction of Tuples and other types are obviously exempt from this rule.
var (name, address, city, zip) = contact.GetAddressInfo();
Example: tuple deconstruction
Local variables have good names
Local variables should have self-evident names too. Every variable should describe its content, and named consistently. One can often reuse the name of the type. Be as specific as necessary and as short as possible.
Declare variables in the innermost scope that is possible
This rule attempts to minimize the number of live variables that must be simultaneously considered. Furthermore, variable declarations should be postponed until enough information is available for full initialization. This rule also reduces the possibility of uninitialized and wrongly initialized variables.
Define variables on declaration
Local variables of primitive type are not initialized. Try to avoid separation of declaration and definition.
// Do not write this!
Money money;
money = GetMoney();
Bad example
Money money = GetMoney();
Better example
var money = GetMoney();
Even better example
This can lead to problems if your variable is initialized differently on a certain condition (if
/switch
). Prefer the use of if
-expressions with the the ternary operator (? :) for simple cases or extract the initialization into a function
var money = HasBank()
? GetFromBank()
: GetCash();
This could also be easily refactored into a function
This not only avoids uninitialized variables, but makes sure you always handle both cases.
With C# 8.0 switch-expressions are introduced which are highly recommended instead of switch-statements.
Do not reuse variables
A variable should have only one single purpose; there is no reason to recycle variables. The variables are abstractions for the human reader and not memory locations for the compiler.
Prefer type inference
Use var to avoid type names, especially those that are noisy, obvious, or unimportant — cases where the type does not aid in clarity for the reader. Only use manifest type declarations when it helps readability.
var textBox = GetTextBox();
textBox.SetText("foo");
Type inference
This does not mean that you should blindly replace types with var.
Do not use magic values
Every number except 0 or 1 needs a useful name derived from its true meaning in the code. However, this does not mean that 0 and 1 cannot have a name too. The same goes for magic strings!
Unused variables with discared values
Ideally we would prefer a clean way with pattern matching, where we always could use _
. Since this is only possible in certain parts of the language, we still try to use the _
for unused variables. If you have more than one unused variable in the same context, use _0
, _1
, … instead.
Basic constructs
Only one expression per line
Multiple expressions or statements in one line increase the mental burden while reading them. Split them up to avoid mentally hiding statements.
If-statements
The condition of the if-statement should have no side effects. Avoid nested if-statements and prefer logical operators.
if (!Move(id))
{
return false;
}
if (!UpdateStart(StartValue))
{
return false;
}
Avoid side-effects
var itemFound = Move(id);
if (!itemFound)
{
return false;
}
var updateIsReady = UpdateStart(StateDelete);
if (!updateIsReady)
{
return false;
}
Better alternative
For-loops
Avoid for-loops and use LINQ and higher order functions as an alternative.
Prefer foreach-loops
Whenever you are iterating over some kind of enumerator, prefer the foreach syntax:
foreach (var value in values.OrderByDescending(v => v))
{
// Do something in reverse order
}
foreach-loop
Switch-statement
Handle all cases
Unhandled cases lead the program into an undefined state.
If you have no natural default case, declare one throwing an ArgumentException
when dealing with an argument or an InvalidOperationException
for all other cases.
Goto
The goto statement is forbidden in all cases.
Expressions
Prefer expressions over statements.
Prefer logical operators to if statements
if (node.GetParenthesis(key))
{
item.Setting = false;
}
else
{
item.Setting = true;
}
Avoid if and ternary operator for logical expressions
Logical expressions have no branches, are more concise and always handle all the logical cases.
item.Setting = !GetParenthesis(key);
Use logical operators !
, &&
and ||
ternary operator
Prefer the ternary operator to simple if statements. The ternary operator is an expression in contrast to the if-statement and it forces you to handle all the cases.
We format the ternary operator like this, to see which branch we are dealing with easily.
return condition
? true-expression
: false-expression;
ternary operator
Switch Expression
Prefer switch expressions over switch statements.
return animalKind switch
{
AnimalKind.Dog => "dog",
AnimalKind.Cat => "cat",
_ => throw new InvalidOperationException($"Unsupported animal kind {animalKind}"),
};
switch expression
Expression-bodied Members
Use the expression body syntax when a member returns a single expression. Move the arrow to the next line when the expression gets too long.
public int Length => 0;
public string AbsolutePath()
=> Path.Combine(CalculateRootPath(), RelativePath);
expression-bodied members
Relational Patterns
We don't keep a space between the separator and the value in relational patterns.
string WaterState(int tempInFahrenheit)
=> tempInFahrenheit switch
{
>32 and <212 => "liquid",
<32 => "solid",
>212 => "gas",
32 => "solid/liquid transition",
212 => "liquid / gas transition",
};
switch expression with relational patterns
Types
Use strong types
C# has a strong typing system, take advantage of it. A strong type will help you a lot in refactoring, and the compiler will easily tell you that you are using the wrong parameter if you do not have a string type like a comma separated list instead of a strong collection like a vector of longs.
public sealed record Vector(int X, int Y, int Z);
Vector CrossProduct(Vector factor1, Vector factor2);
// Never do this
bool CrossProduct(long x1, long y1, long z1, long x2, long y2, long z2,
out long resultX, out long resultY, out long resultZ);
Create a strong type
New type idiom
The idea behind the new type idiom is to have additional type information, even if you have a single integer as an argument. Even when we have simple integers, it usually represents a concept not as generic. This concept could be years, bytes, money or position. So even if you have a single integer, you can create a new type from your abstract concept.
public sealed record Years(int Value);
Declaring the new type Years
In this case we have a time period in years. The constructor should be explicit, otherwise a Year object would be automatically constructed from a given integer. That way you need to be explicit when you call a function like this:
bool OldEnough(Years years);
Use the new type
The Function OldEnough
only accepts years. If you give an integer or a value of type Days
you will get a compilation error. This helps in self-documentation and avoids misunderstandings between programmers with different assumptions. The code the compiler produces is the same with or without the additional type information. The additional Type-Information is just a compile time hint for the programmer.
Do not convert the types from the API if not necessary
Casting types can lead to unexpected behavior and should be avoided, if you need to convert types, chose a place where it minimizes the number of casts.
Define appropriate types
If two or more items of data belong together, try to find a name for them, and define a type you can reuse. Only use Tuple when the data is generic or part of an external interface.
Use appropriate data-structures
Do not pass around data structures as strings or tuples, when you could create a better type for your data.
Use Readonly PODs for data
All PODs must inject their state in the constructor and provide read only properties to it. A public setter is forbidden, a private setter is discouraged.
Make PODs easy to use
PODs should implement IEquatable
or IComparable
, prefer records over manual implementations.
Functional Programming
Immutability
While only data can have side effects, it is the methods and functions, which profit the most of immutability. They are much easier to understand and usually the code is much more elegant when you stay away from mutability. A function f in program P has no side-Effect if f() == f()
for all states of P. In addition, the state of P does not change if you invoke f.
Programs without side effects are easy to test because it implies that any test of f is independent of the state of P.
Prefer immutable types to mutable ones
An immutable type can only set its state through a constructor. No stateful setters are allowed on an immutable type. Changing state is done through transformation by copying. This way, even a setter can be written in an immutable way:
Use higher-order functions with LINQ and avoid writing your own for-loops
We try to avoid unnecessary repetition. For many things, there are generic algorithms. Use LINQ and other Algorithms to create powerful abstractions. If there is not an algorithm available, try to be generic and write your own algorithms.
Task | LINQ |
---|---|
Projection | Select, SelectMany |
Aggregation | Aggregate, Sum, Min, Max, Average, Count |
Restriction | Where |
Existence | Any, All, Contains, Distinct |
Set Operations | Contains, Distinct, Union, Intersect, Except |
Sorting | OrderBy, ThenBy, OrderByDescending, ThenByDescending |
Paging | First, Last, Single, Skip, Take, SkipWhile, TakeWhile |
String Formating | string.Format() or use $"" (String interpolation) |
LINQ syntax
Extension Methods and LINQ syntax are equivalent. Both have their up- and down-sides. Chose the one which you think is most appropriate for the Task at hand. You can also mix them in the same code file if it makes the code easier to read.
Use the import of static functions to your advantage
With the ability to import static functions, the syntax is much more natural to call functions on their own. Use that to your advantage and write free functions independent of any type.
Object oriented programming
Abstraction
Depend on abstractions not implementations
Classes which are not PODs must depend on abstractions and not implementations if possible.
Create your own abstractions if none are available
If the library you are using do not offer their own abstractions, create a facade with your own abstractions in front of the implementation.
Factories
Avoid the usage of new, either inject types through the constructor or use factories to create objects on runtime. All Factories should be declared via a delegate in the form of:
public delegate ReturnValue ReturnValueFactory(Parameters parameters);
Signature of factories
Inheritance
- If the relation between two entities can be described as a "Entity A has B"-relation then composition is to be used. You should only use inheritance if you are dealing with a "Entity A is a kind of B"-relation. See: Composition over inheritance.
- Prefer inheriting from interfaces
- Keep your inheritance hierarchy flat
- avoid abstract classes
Encapsulation
Data-Members are by default private
Classes are meant to hide the implementation details; this also means that your data should be hidden from the outside. If you really need public data-members, you might want to think about separating the data into a POD class.
Use the most restricitive visibility modifier possible
Information should be encapsulated as much as possible, which means in first instance it should be private.
public fields are forbidden
We only use properties for public interfaces for PODs and other classes.
Polymorphism
Use the strategy Pattern for different implementations of the same algorithm
The strategy pattern embodies two core principles of object-oriented programming, it encapsulates the concept that varies and lets programmers code to an interface, not an implementation.
Use the different forms of polymorphism to your advantage
C# supports runtime subtype polymorphism with virtual Methods, but there are other forms of polymorphism in C#.
The other form is the ability to overload methods with different parameter types which is a form of static compile time polymorphism. It is more powerful in the regard that you can overload on any type and you do not need to have a type hierarchy for it to work. However be careful on this, the overload resolution uses the static type-definition and not the run-type type definition. Which means this is limited to cases where you exactly know the type at compile time.
Use the visitor pattern for double dispatch
Static overload resolution can dispatch on more than one type, but at runtime we are limited to a single dispatch mechanism offered by the subtype polymorphism. To overcome this, we need to take advantage of both forms of polymorphism offered in C#. The visitor pattern is the right choice in such cases.
Algebraic Datatypes
Algebraic datatypes are also called discriminated unions, tagged unions, and sometimes "or types" colloquially. They can be found in many functional languages (even if they're not named algebraic datatypes specifically), and have become widely known and used in Typescript 2.0.
Algebraic datatypes in C#
While we don't have language support for algebraic datatypes in C#, there are some patterns on how you can implement something that behaves like one.
We recommend the usage of the following pattern using an abstract base class with a private constructor and inner, deriving classes and usually a match function. This has the advantage that the algebraic options are namespaced as the inner class, which simplifies the naming. The constructor is private to prevent extension of the algebraic datatype — with a private constructor only inner classes can derive from the abstract base class.
When your intention is to write something like an enum, but with attachable data to every option, algebraic datatypes is what you're looking for.
Example 1 (UpdateMode
)
Our example has three different update modes - the ServerDefault
, the Auto
mode via ChannelName
and a Version
defined Pinned
mode.
Our match method allows us to define what should happen for each configured mode.
The upside of that over a switch expression is that we have no problems with exhaustability (see Example 2).
public abstract class UpdateMode
{
private UpdateMode()
{
}
public abstract TResult Match<TResult>(
Func<ServerDefault, TResult> serverDefault,
Func<Auto, TResult> auto,
Func<Pinned, TResult> pinned);
public sealed class ServerDefault : UpdateMode
{
public override TResult Match<TResult>(
Func<ServerDefault, TResult> serverDefault,
Func<Auto, TResult> auto,
Func<Pinned, TResult> pinned)
=> serverDefault(this);
}
public sealed class Auto : UpdateMode
{
public Auto(string channelName)
{
ChannelName = channelName;
}
public string ChannelName { get; }
public override TResult Match<TResult>(
Func<ServerDefault, TResult> serverDefault,
Func<Auto, TResult> auto,
Func<Pinned, TResult> pinned)
=> auto(this);
}
public sealed class Pinned : UpdateMode
{
public Pinned(string version)
{
Version = version;
}
public string Version { get; }
public override TResult Match<TResult>(
Func<ServerDefault, TResult> serverDefault,
Func<Auto, TResult> auto,
Func<Pinned, TResult> pinned)
=> pinned(this);
}
}
Summed up:
- Abstract class (ensures inner classes are always used in a namespaced manner, e.g.
UpdateMode.Auto
) - Private constructor in abstract class (this ensures no one can derive from the abstract class except the inner classes)
- An abstract match method with a
Func<Variant, TResult>
for every option of the algebraic datatype - Inner deriving sealed classes (that implement match and call the base constructor)
- You can have data either on the base class and have it passed to the base constuctor, or in the deriving classes
Example 2 - why we recommend a match function
Consider the UpdateMode
algebraic datatype from Example 1.
Usage example with Match:
var info = mode.Match(
serverDefault => $"Server default mode",
auto => $"Auto with channel {auto.ChannelName}",
pinned => $"Pinned to Version {pinned.Version}");
Usage example with switch expression:
var info = mode switch
{
UpdateMode.ServerDefault serverDefault => $"Server default mode",
UpdateMode.Auto auto => $"Auto with channel {auto.ChannelName}",
UpdateMode.Pinned pinned => $"Pinned to Version {pinned.Version}",
_ => throw new Exception("Unreachable"), // we almost always need this, because the compiler doesn't know there's only 3 types
};
The advantage of using a match function is clear: No useless exception that is unreachable anyways, and immediate feedback from the compiler when adding another option to the algebraic datatype.
Use lables on the match method
It is good practice to use argument labels to avoid mixups in argument order, so consider this example superior to the Match example in Example 2:
var info = mode.Match(
serverDefault: serverDefault => $"Server default mode",
auto: auto => $"Auto with channel {auto.ChannelName}",
pinned: pinned => $"Pinned to Version {pinned.Version}");
This is especially true if you don't need the argument itself and just execute an action:
// This example uses ActionToUnit and NoOperation from Funcky.
// void is not a valid generic argument for a method, so we often use the Unit type for some of these use cases.
// See the Funcky documentation (https://polyadic.github.io/funcky/) for more information on the Unit type.
static void SomeMethod() => NoOperation();
mode.Match(
serverDefault: _ => ActionToUnit(SomeMethod),
auto: _ => ActionToUnit(SomeMethod),
pinned: _ => ActionToUnit(SomeMethod));
Disadvantages of using a match function, or why you might want your match function to be internal
When the match function is exposed from a library, adding a new variant to the algebraic datatype will break public api. Therefore, if you want to avoid this, you should make the match method internal. This makes you losing out on the exhaustiveness when the library is used, but at least you don't have to break public api to add another option.
Methods
Usually keep a method signature on one line
Alternativly get each parameter on a single line, especially for constructors where the injected objects can change often.
void Signature(int foo, string bar);
Function signature
Write short methods
Prefer small and focused functions. We recognize that long functions are sometimes appropriate, so no hard limit is placed on functions length. If a function exceeds about 30 lines, think about whether it can be broken up without harming the structure of the program. Even if your long function works perfectly now, someone modifying it in a few months may add new behavior. This could result in bugs that are hard to find. Keeping your functions short and simple makes it easier for other people to read and modify your code. You could find long and complicated functions when working with some code. Do not be intimidated by modifying existing code: if working with such a function proves to be difficult, you find that errors are hard to debug, or you want to use a piece of it in several different contexts, consider breaking up the function into smaller and more manageable pieces.
Output parameters
Output parameters are forbidden. They are bad style and not necessary. Return multiple values as a tuple or your own better datastructure.
(ReceiverChannel, SendingChannel) CreateChannels();
var (rx, tx) = CreateChannels();
Multiple return values
Ref parameters
Ref parameters are forbidden in our own API.
Namespaces
Nothing should be in the global namespace.
- The first level namespace is Messerli
- The second level namespace reflects the module you are working in
Exceptions
Exceptions are a good tool to signal behavior out of the ordinary.
Use Nullable (?
) types if you need to represent values with a no-value state
Do not use bools to indicate illegal state, as they can easily be ignored. Wrapping a returned type in an nullable value forces the caller to check for the state. Ideally use them in a monadic way with an appropriate library.
Do not use exceptions for control flow
Exceptions should be used to signal exceptional behavior, such as incorrect user input or missing resources. Using them for (expected) control flow is analogous to using a goto statement.
Miscellaneous
Use the Humble Object Pattern for System boundaries like UI
At the boundaries of the system, where things are often difficult to test use the humble object pattern to make it better testable. We accomplish the pattern by reducing the logic close to the boundary, making the code close to the boundary so humble that it doesn't need to be tested. The extracted logic is moved into another class, decoupled from the boundary which makes it testable.
Use the type system
Unit tests became extremely popular with dynamically typed languages like Ruby and JavaScript out of necessity. Those languages became popular initially for relatively small code fragments for automation or simple tasks. They were not designed as system languages with millions of lines of code in mind. But these languages became popular and the ecosystem grew and therefore also the need for refactoring in bigger projects. Since the type system were weak the compiler or run-time could not help much in finding defects. Unit tests do much more than just checking if the types are in the expected range, and if a function is correctly typed, but it is one kind of unit tests we do not need in statically typed languages. However it is important that you actually use the type System to your advantage.
Avoid regions
Regions can distract and hide things from you, that is why regions are generally discouraged except for automatically generated code, you wouldn't want to change by hand.
Comments
Though a pain to write, comments are vital to keeping our code readable. The following rules describe what you should comment and where. However, remember: while comments are very important, the best code is self-documenting. Giving sensible names to types and variables is much better than using obscure names that you must then explain through comments. When writing your comments, write for your audience. The next contributor who will need to understand your code. Be generous - the next one may be you!
Language
Comments are written in English.
Style
We only use double and triple-slash comments. We do not use the /* */ - style comments. Write your comments above your code you want to comment. Only write comments on the right side of the code when doing so would benefit the readability greatly and the code in question is very short, e.g. an initialization. If your code describes a block of code, consider refactoring it into a function.
XML comments
Try to document your public interface.
- The comments for an interface should be only in the header file.
- Try avoiding stating the obvious.
- We use XML-comments to document the interface.
/// <summary>
/// Specifies that <see cref="Open"/> should open an existing file.
/// When the file is opened, it should be truncated so that its size is zero bytes.
/// Requires that <see cref="Write"/> is called too.
/// Can not be used to together with <see cref="Append"/>.
/// </summary>
IFileOpeningBuilder Truncate(bool truncate = true);
Commenting a function
We want to adhere closely to this style because Visual Studio can read and interpret the XML comments, and IntelliSense will give you a real-time information on ctrl-space. As you can see, you do not only get the general summary, but a value-by-value reference of the parameters.
Write useful comments
A comment is useful, if it helps to understand the code better than without. Comments are not useful if they state the obvious or are repeating what the code already tells you. Comments are especially useful if you do something out of the ordinary, or complex. A high-level description of an algorithm for example is usually a good idea.
ASCII Art seperators are forbidden in code
For visual seperation you can use a #pragma region declaration.
A region can be folded for increased visibility:
Comments on curly brace
There are no comments on the ending curly brace. Your IDE can do code folding
Do not commit code that is commented out
Code that is not compiled will rot and will be useless soon. If you really need the code, why is it not compiled? Delete the code. If you really need it again, you can always use your source control system.
Commit Message
We follow six of the seven rules of great commit messages
- Separate subject from body with a blank line
- Limit the subject line to 50 characters
- Capitalize the subject line
- Do not end the subject line with a period
- Use the imperative mood in the subject line
- Use the body to explain WHAT and WHY vs. HOW
The rule "Wrap the body at 72 characters" does not apply to TFVC users, as Visual Studio takes care of wrapping the body correctly.
In addition, we prepend the commit message with the module that has been worked on, followed by a colon. This prefix does not count towards the subject line character limit.
Language
Commit messages are written in English.
Bad example: Added feature
Good example: Document Editor: Add copy-pasting feature
Bad example: A position with a parts list can be inserted in your own parts list, which leads to a program crash.
Good example: Catalog: Fix Crash happening in recursive parts list ↵
A position which had itself a parts list could wrongly be inserted into a parts list.
Initializer
Index Initializers
Use index initializers with dictionary. This elegantly prevents uninitialized dictionaries. See Object and Collection Initializers (C# Programming Guide).
var dict = new Dictionary<string, int>
{
["key1"] = 1,
["key2"] = 50,
};
Good example
var dict = new Dictionary<string, int>();
dict["key1"] = 1;
dict["key2"] = 50;
Not so good example
var dict = new Dictionary<string, int>
{
{ "key1", 1 },
{ "key2", 50 },
};
Deprecated way example