This post is mostly about how to write code — good habits matter.
Reposted from: Yin Likun’s personal site (the president of our software R&D club).
1. Introduction
This is a set of development conventions to follow when developing as a C# programmer or C# developer. Following it brings these benefits:
-
Consistency in how code is written.
-
Better readability and maintainability.
-
Code sharing between programmers when a project is built by a team.
-
Easier code review.
This is a first edition, covering only general cases, and it can’t cover every situation.
2. File Organization
2.1 C# Source Files
Keep class names and file names short, no more than 2000 LOC, and split the code so the structure stays clear. Put each class in its own file and name the file after the class (with a .cs extension, of course). This convention makes everyone’s work easier.
2.2 Directory Layout
Create one directory per namespace. (Use MyProject/TestSuite/TestTier as the path for MyProject.TestSuite.TestTier, rather than using the dotted namespace name as the path.) That makes mapping namespaces onto the directory hierarchy much easier.
3. Indentation
3.1 Line Wrapping
When an expression doesn’t fit on one line, follow these general principles:
-
Break after a comma.
-
Break after an operator.
-
Break at a higher level rather than a lower one.
-
Align the wrapped line with the start of the expression at the same level on the previous line.
Example of wrapping a method call:
longMethodCall(expr1, expr2,
expr3, expr4, expr5);
Example of wrapping an arithmetic expression. Preferred:
var = a * b / (c - g + f) +
4 * z;
Bad formatting — avoid:
var = a * b / (c - g +
f) + 4 * z;
The first method is recommended because it breaks outside the parenthesized expression (break at a higher level). Note: use tabs to reach the indentation position, then spaces to reach the wrap position. In our example:
> var = a * b / (c - g + f) +
> ......4 * z;
”>” is a tab, ”.” is a space. (Whitespace after a tab is indentation with tabs.) A good coding habit is to make your editor show tabs and spaces.
3.2 Whitespace
There has never been a uniform standard for indentation width. Some people like two spaces, some four, some eight, and some even more. The better practice is tabs. Tabs have some advantages:
-
Everyone can set their own preferred indentation level.
-
It’s just one character instead of 2, 4, 8, etc., so it reduces typing (even with auto-indent, sometimes you have to set or unset indentation by hand, and so on).
-
If you want to increase or decrease indentation, select a block and use Tab to increase and Shift-Tab to decrease. That works in almost any text editor.
Here we define the tab as the standard indentation character.
Don’t indent with spaces — use tabs!
4. Comments
4.1 Block Comments
Block comments should generally be avoided. /// comments are recommended as the C# standard declaration. If you do want a block comment, use this style:
/* Line 1
* Line 2
* Line 3
*/
That lets readers distinguish comment blocks from code blocks. C-style single-line comments aren’t encouraged, but you can still use them. If you do, put a line break after the comment line, because code preceded by a comment on the same line is hard to read:
/* blah blah blah */
Block comments are useful in rare cases. Usually they’re used to comment out large sections of code.
4.2 Single-Line Comments
You should use the // style to “comment out” code (shortcut Alt+/). It can also be used for the commenting part of code.
Single-line comments used to explain code must be indented to the matching indentation level. Commented-out code should be placed on the first commented line so it’s easier to see.
A rule of thumb: a comment shouldn’t be much longer than the code it explains, because that means the code is too complex and has a potential bug.
4.3 File Comments
In the .NET Framework, Microsoft introduced a file-comment format based on XML. These are regular single-line C# comments containing XML tags. They follow the single-line comment pattern:
/// <summary>
/// This class...
/// </summary>
Multi-line XML comments follow this pattern:
/// <exception cref="BogusException">
/// This exception gets thrown as soon as a
/// Bogus flag gets set.
/// </exception>
To count as an XML comment line, every line must start with three slashes. There are two kinds of tags:
-
Documentation items
-
Formatting / references
The first kind includes tags like <summary>, <param>, or <exception>. These documentation items describing a program’s API elements must be written clearly for other programmers. As the multi-line example above shows, these tags usually carry a name or cref attribute. The compiler checks these attributes, so they must be valid and correct. The second kind uses tags such as <code>, <list>, or <para> to control the layout of remarks.
Documentation can be generated from the “Create” menu item in the “File” menu. The output is HTML.
5. Declarations
5.1 Number of Declarations Per Line
One declaration per line is recommended, since it makes commenting easier.
int level; // indentation level
int size; // size of table
When declaring variables, don’t put multiple variables or variables of different types on one line, for example:
int a, b; //What is "a"? What does "b" stand for?
The example above also shows the flaw of unclear variable names. Be clear when naming variables.
5.2 Initialization
Initialize local variables as soon as they’re declared. For example:
string name = myObject.Name;
or
int val = time.Hours;
Note: if you initialize a dialog, use a using statement by design:
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
...
}
5.3 Class and Interface Declarations
When writing C# classes and interfaces, follow these formatting rules:
-
Don’t put a space between the method name and the parenthesis
(that begins its parameter list. -
Start the brace
{on the line after the declaration statement. -
End with
}, matching the indentation of its opening brace.
For example:
class MySample : MyClass, IMyInterface
{
int myInt;
public MySample(int myInt)
{
this.myInt = myInt;
}
void Inc()
{
++myInt;
}
void EmptyMethod()
{
}
}
For brace placement, see section 10.1.
6. Statements
6.1 Simple Statements
Every line should contain only one statement.
6.2 Return Statements
Don’t use the outermost parentheses in a return statement. Not this:
return (n * (n + 1) / 2);
This:
return n * (n + 1) / 2;
6.3 If, if-else, if else-if else Statements
if, if-else, and if else-if else statements should look like this:
if (condition)
{
DoSomething();
...
}
if (condition)
{
DoSomething();
...
}
else
{
DoSomethingOther();
...
}
if (condition)
{
DoSomething();
...
}
else if (condition)
{
DoSomethingOther();
...
}
else
{
DoSomethingOtherAgain();
...
}
6.4 for / foreach Statements
A for statement should look like this:
for (int i = 0; i < 5; ++i)
{
...
}
Or on one line (consider using a while statement instead):
for (initialization; condition; update);
A foreach statement should look like this:
foreach (int i in IntList)
{
...
}
Note: inside a loop, braces are usually used even when there’s only one statement.
6.5 While / do-while Statements
A while statement should be written as:
while (condition)
{
...
}
An empty while statement should be:
while (condition);
A do-while statement should be:
do
{
...
} while (condition);
6.6 Switch Statements
A switch statement should look like this:
switch (condition)
{
case A:
...
break;
case B:
...
break;
default:
...
break;
}
6.7 Try-catch Statements
A try-catch statement should follow this format:
try
{
...
}
catch (Exception) {}
or
try
{
...
}
catch (Exception e)
{
...
}
or
try
{
...
}
catch (Exception e)
{
...
}
finally
{
...
}
7. Blank Lines
7.1 Blank Lines
Blank lines improve readability. They separate blocks of code that are logically related to each other. Two blank lines should be used between:
-
Logical sections of a source file.
-
Class and interface definitions (define only one class or interface per file to avoid this case).
One blank line should always be used between:
-
Methods
-
Properties
-
A method’s local variables and its first statement
-
Logical sections within a method, to improve readability. Note that blank lines must be indented since they contain a statement — this makes inserting them easier.
7.2 Internal Spacing
There should be a space after a comma or a semicolon, for example:
TestMethod(a, b, c);
Not:
TestMethod(a,b,c)
or
TestMethod( a, b, c );
Surround operators with a single space (except unary operators like plus and logical not), for example:
a = b; // don't use a=b;
for (int i = 0; i < 10; ++i) // don't use for (int i=0; i<10; ++i)
// or
// for(int i=0;i<10;++i)
7.3 Table Formatting
A logical block of lines should be formatted as a table:
string name = "Mr. Ed";
int myValue = 5;
Test aTest = Test.TestYou;
Use spaces rather than tabs for table formatting, because with some tab indentation settings, table formatting looks strange.
8. Naming Conventions
8.1 Capitalization Styles
8.1.1 Pascal Casing
Capitalize the first letter of every word (as in TestCounter).
8.1.2 Camel Casing
Capitalize the first letter of every word except the first (for example testCounter).
8.1.3 All Uppercase
Use all uppercase only for identifiers made of one- or two-character abbreviations. Identifiers of three or more characters should use Pascal casing instead. For example:
public class Math
{
public const PI = ...
public const E = ...
public const feigenBaumNumber = ...
}
8.2 Naming Guidelines
In general, using underscore characters in names, per these guidelines, is considered bad practice for Hungarian notation.
Hungarian notation is a set of prefixes and suffixes applied to names to map variable types. This naming style was widely used in early Windows programs but has been dropped — or at least discouraged. If you follow this guide, Hungarian notation is not allowed.
But remember: a good variable name describes semantics without losing the type.
One exception to this rule is GUI code. Including GUI elements like buttons, all fields and variable names should carry a suffix of their type name, not an abbreviation. For example:
System.Windows.Forms.Button cancelButton;
System.Windows.Forms.TextBox nameTextBox;
8.2.1 Class Naming Guidelines
-
Class names must be nouns or noun phrases.
-
Use Pascal casing (see 8.1.1).
-
Don’t use any class prefix.
8.2.2 Interface Naming Guidelines
-
Name interfaces with a noun, noun phrase, or adjective that describes behavior (for example
IComponentorIEnumerable). -
Use Pascal casing (see 8.1.1).
-
Prefix the name with
I, followed immediately by an uppercase letter (the first letter of the interface name).
8.2.3 Enum Naming Guidelines
-
Use Pascal casing for enum value names and type names.
-
No prefixes on enum types or values.
-
Use a singular name for enums.
-
Use a plural name for bit fields.
8.2.4 Naming Read-Only and Constant Fields
-
Name static fields with nouns, noun phrases, or abbreviations of nouns.
-
Use Pascal casing (see 8.1.1).
8.2.5 Naming Parameters / Non-Constant Fields
-
Do use descriptive names that convey the meaning of the variable and its type. But a good name should be based on the parameter’s meaning.
-
Use Camel casing (see 8.1.2).
8.2.6 Variable Naming
-
Counter variables are best called
i,j,k,l,m,nwhen used in trivial counting loops. (See 10.2 for smarter naming of global counters, etc.) -
Use Camel casing (see 8.1.2).
8.2.7 Method Naming
-
Name methods with verbs or verb phrases.
-
Use Pascal casing (see 8.1.2).
8.2.8 Property Naming
-
Name properties with nouns or noun phrases.
-
Use Pascal casing (see 8.1.2).
-
Consider naming a property with the same name as its type.
8.2.9 Event Naming
-
Name event handlers with an event handler suffix.
-
Name the two parameters
senderande. -
Use Pascal casing (see 8.1.1).
-
Name event arguments with an
EventArgssuffix. -
Name events with prefix and duplicate concepts in present and past tense.
-
Consider naming an event with a verb.
8.2.10 Capitalization Summary
| Type | Case | Notes |
|---|---|---|
| Class / Struct | Pascal Casing | |
| Interface | Pascal Casing | Starts with I |
| Enum values | Pascal Casing | |
| Enum type | Pascal Casing | |
| Events | Pascal Casing | |
| Exception class | Pascal Casing | End with Exception |
| public Fields | Pascal Casing | |
| Methods | Pascal Casing | |
| Namespace | Pascal Casing | |
| Property | Pascal Casing | |
| Protected/private Fields | Camel Casing | |
| Parameters | Camel Casing |
9. Programming Practices
9.1 Visibility
Don’t make any public instance or class variables; keep them private. For private members it’s best to write nothing rather than use the “private” modifier. Private is the default, and every C# programmer should know that.
Use properties instead. You may use public static (or const) as an exception to this rule, but it shouldn’t be the rule.
9.2 No “Magic” Numbers
Don’t use magic numbers — that is, literal constant values used directly in source. Replacing them guards against change (say your application can handle 3540 users instead of 427, and your code spreads 25000 LOC across 50 lines) and avoids errors with no benefit. Declare a constant with the number instead:
public class MyMath
{
public const double PI = 3.14159...
}
10. Coding Examples
10.1 Brace Placement Example
namespace ShowMeTheBracket
{
public enum Test
{
TestMe,
TestYou
}
public class TestMeClass
{
Test test;
public Test Test
{
get
{
return test;
}
set
{
test = value;
}
}
void DoSomething()
{
if (test == Test.TestMe)
{
//...stuff gets done
}
else
{
//...other stuff gets done
}
}
}
}
Braces should start on a new line after:
-
Namespace declarations (note this is new in version 0.3 and differs from 0.2)
-
Class / interface / struct declarations
-
Method declarations
10.2 Variable Naming Example
Instead of:
for (int i = 1; i < num; ++i)
{
meetsCriteria[i] = true;
}
for (int i = 2; i < num / 2; ++i)
{
int j = i + i;
while (j <= num)
{
meetsCriteria[j] = false;
j += i;
}
}
for (int i = 0; i < num; ++i)
{
if (meetsCriteria[i])
{
Console.WriteLine(i + " meets criteria");
}
}
Try intelligent naming:
for (int primeCandidate = 1; primeCandidate < num; ++primeCandidate)
{
isPrime[primeCandidate] = true;
}
for (int factor = 2; factor < num / 2; ++factor)
{
int factorableNumber = factor + factor;
while (factorableNumber <= num)
{
isPrime[factorableNumber] = false;
factorableNumber += factor;
}
}
for (int primeCandidate = 0; primeCandidate < num; ++primeCandidate)
{
if (isPrime[primeCandidate])
{
Console.WriteLine(primeCandidate + " is prime.");
}
}
Note: indexer variables are usually called i, j, k, etc. But in a case like this, it makes sense to reconsider that principle. In general, when the same counter or indexer is reused, give them meaningful names.
