Coding Style
Indentation
Use tabs, not spaces, to indent your
code. Indent one tab with each deeper
level of code block or construct. In
general, when you open a code block with a bracket “{“, the next line will
likely be further indented by one tab.
Example:
Class TestClass
{
// New indentation (tab) level
{
// New indentation (tab) level
public void SomeMethod()
{
// New
indentation (tab) level
if (...)
{
// New
indentation (tab) level
}
}
}
}
Braces
Use the vertical pair-matching style of
bracing. Example:
if (currentIndex >= INDEX_MAX)
{
return
_partNames[INDEX_MAX];
}
else
{
return
_partNames[currentIndex];
}
In addition, while braces are syntactically
optional for constructs such as if
and for having only one line to
execute, include the braces for readability and maintainability (as in the
above example.)
Empty code regions can be represented with
“{}” on the same line. For example:
public class MyClass
{
{
private MyClass() {}
...
}
Also, property get and set accessors,
when they contain a single line of code, can optionally be written on the same
line for brevity. For example:
public string AgeInYears
{
{
get {return _ageInYears;}
set {_ageInYears = value;}
}
}
Data Type Declarations
When specifying data types, such as for
variable declarations or method return values, use the system keyword aliases
(e.g. int, float, string) instead of the base types (e.g. System.Int32, System.Single,
System.String).
Example:
int i; // Not Int32
or System.Int32
string userName; // Not
String or System.String
char c = ‘x’; // Not Char
or System.Char
Placement of Variable Declarations
Declare variables as closely as possible to
first use and within the deepest possible scope. A benefit of this is that as code is modified
over time, variable declarations and instantiations are less likely to be
orphaned at the top of pages or methods.
Tip - For help finding a
declaration of a variable in Visual Studio.Net, simply right-click on the
variable name and choose “Go To Definition”.
Example:
public string GetPartNameLameExample(int partID)
{
// NOT HERE
// string partName;
if (partID !=
MAGIC_PART_NUMBER)
{
// HERE – scope to deepest possible block
string partName = GetPartName(partID);
if
(partName.Length > 20)
{
return
partName.Substring(0,17) + “...”;
}
return partName;
}
else
{
return
MAGIC_PART_NAME;
}
}
This also applies to “for” loops and
similar constructs:
for (int i=0; i <= someValue; i++)
{
// variable “i” is
scoped to this block only
}
Constants
Do not use “magic numbers” in your
code. Replace with constants and your
code will be more readable and maintainable.
Example:
int timeout = hours * 3600; // NO, NO, NO!
const int SECONDS_PER_HOUR = 60 * 60;
int timeout = hours * SECONDS_PER_HOUR;
const int SECONDS_PER_HOUR = 60 * 60;
int timeout = hours * SECONDS_PER_HOUR;
Do not create public constants in classes,
instead use static readonly fields. Constants can be compiled into referencing
assemblies, so a change to the original would require recompilation of the
consuming assembly to effect a change.
Namespaces and the using Keyword
Avoid using fully-qualified type names in
your code. Instead, add a using keyword with the appropriate
namespace at the top of your code and use the shortened version.
Do not use using keywords inside a namespace declaration.
Order using
keywords alphabetically, but group them by listing standard Microsoft
references first, then custom or third party references after:
Example
using System;
using System.Data;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using ThirdParty.Tool;
using Global.Database;
using Global.Translations;
using Monster.User;
namespace Monster.Example
{
...
}
using System.IO;
using ThirdParty.Tool;
using Global.Database;
using Global.Translations;
using Monster.User;
namespace Monster.Example
{
...
}
Avoid having extraneous using
keywords of namespaces that you aren’t actively calling in your code (for
example, Visual Studio includes a number of default usings to each new
code file, some can be removed if you aren’t using that specific functionality,
such as System.Web.SessionState.)
While they won’t cause any problems, a shorter list is simply easier to
read and maintain.
Enumerations and the Flags
Attribute
If an enumeration represents combinable
settings (e.g. bitmasks), mark the enum with the [Flags] attribute above the
definition. Also, per the section on
naming, use a plural name for the enumeration itself. Note that [Flags] does not assign values, you
still should set the values manually (as in the example below.)
Examples
[Flags]
public enum Bindings
{
public enum Bindings
{
IgnoreCase = 0x01,
NonPublic = 0x02,
Static = 0x04,
Static = 0x04,
All = IgnoreCase | NonPublic | Static
}
}
Switch Statements
When using a switch statement that
would normally not require a default case, ensure that you add one which
throws an exception and/or asserts false.
This is especially true when your switch argument is based on an
enumeration – another developer could extend that enumeration and your switch
statement would not have had that specific value as a case.
switch (someValue)
{
{
case Things.Alpha:
...
break;
break;
case Things.Beta:
...
break;
default:
throw new Exception(String.Format(“Unknown
\”Things\” enumeration value: \”{0}\”.”, someValue));
}
}
Conditional Methods
Avoid compiling out methods using
#if/#endif constructs. Instead, use the Conditional
attribute under System.Diagnostics.
Note that methods decorated in this way must return void because
they, and the code that calls them, are compiled out if the conditional
attribute is false.
Attributes
Place all assembly-level attributes (e.g. [assembly: FileIOPermission(…)] ) in AssemblyInfo.cs.
Parameter Declaration (Reference
vs. Value)
By default, parameters to methods are
passed by value. For value types (int, byte,
long, etc.), this is straightforward,
a copy of the value is sent, if changed in the method, there is no effect to
the original variable. Reference types
(e.g. a variable pointing to a DataSet
instance,) have a copy of their reference
(or pointer) passed by value, not the object.
This means the method has access to and
can change the referenced object via the shared pointer.
Use
parameter qualifiers to identify and limit the use of a parameter whenever
possible. If a parameter may change in
the call, and could be returned modified, use ref. However, instead of
simply declaring a parameter as ref
when it is not read, only written to, use out
instead.
How does ref affect a reference type? The only difference between “MyMethod(ref
Customer c)” and “MyMethod(Customer c)” is that if the method assigned a different instance of a Customer class
to c, the ref version would also
assign that new instance in the caller’s reference, the non-ref version would be assigned only
locally to the method and (typically) destroyed when the reference goes out of
scope at the end of the method call.
Keep in
mind that while a string is a reference type, it is immutable, so if you pass a
string reference by value and assign a new string value, you are actually
assigning a new string instance and therefore the change will not affect the
caller. To have the new string reflected
in the caller, use the ref keyword.
[For a proper description, far better than
this, see “Value and Reference Parameters” in Inside C#, 2nd
Edition by Archer and Whitechapel.]
Critical Regions and Multithreaded
Operations
In multithreaded applications, a critical
region is a block of code allowing only one thread to execute at a time. To create a critical region, use the lock
statement and target the current object instance with the this keyword:
lock(this)
{
// critical code
}
The above will lock the region from all
threads using the same instance of the class. There are times when you want to ensure a
region is synchronized across all instances of the class. To do this, you can lock on a private static
reference-type variable. If you do not
already have one, create a “dummy” one specifically for locking use:
private static object _lockVariable =
new object();
...
lock(_lockVariable)
{
// critical code
}
Warning: Never
write code that locks a Type object:
lock(typeof(TargetClassName)) // No!
Evil!!!
{
// critical code
}
This is a commonly documented approach to
locking a region for all instances of a class, but unfortunately suffers from a
number of issues as described in: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaskdr/html/askgui06032003.asp Use the private static variable approach
described above instead.
Checked and Unchecked Arithmetic
Operations
To improve performance, arithmetic and
casting operations in C# are, by default, performed in unchecked mode,
where overflows result in truncation of the final result. Truncation is done by dropping any high-order
bits outside the limit of the target type.
This generally results in values that are not useful. For example:
short s = 257;
byte b = (byte) s;
byte b = (byte) s;
The value of b is 1 after the
cast. Why? The short value 257 is bit 9 (=256)
and bit 1 (0000 0001 0000 0001.) Since
bit 9 is outside the scope of short (which is only 8 bits,) it is
dropped, leaving just bit 1 set (0000 0001.)
If you have equations or casts which may
truncate, wrap the code in a checked region and handle any resulting OverflowException. If you are certain that equations cannot
result in overflow or underflow, use the default unchecked context. Avoid the “/checked” project compilation
option, but if you must use it, note the fact in your code documentation.
Example:
int x = Convert.ToInt32(Console.ReadLine());
int y = Convert.ToInt32(Console.ReadLine());
int uncheckedTotal = x + y; // This would never throw an error, but might truncate
int y = Convert.ToInt32(Console.ReadLine());
int uncheckedTotal = x + y; // This would never throw an error, but might truncate
short a = (short) x;
// This would never throw an error, but might truncate
try
{
int total = checked(x + y); // This might fail, throwing OverflowException
short b = checked((short) x); // This might fail, throwing OverflowException
}
catch(OverflowException exp)
{
// Handle case
}
try
{
int total = checked(x + y); // This might fail, throwing OverflowException
short b = checked((short) x); // This might fail, throwing OverflowException
}
catch(OverflowException exp)
{
// Handle case
}
Case Sensitivity
While C# is case-sensitive, classes and web
services may be exposed to environments that are not case-sensitive (e.g.
VB.Net.) Therefore, do not name entities
of the same level (e.g. namespace, method, property) that differ only in
case. For example, having two
properties, “userID” and “UserID” will work in C#, but will not be distinguishable
in VB.Net and will result in a non CLR-compliant assembly.
Namespaces
Namespace Naming
The namespace of a custom object should
begin with the name of the major group under which the work is being done. There are three general rules for root
naming:
1.
Classes that will be used only
internally to a project or application should be named with the root of
“<Company Segment>.<ProjectName or Abbreviation>”. For example, “MonsterMoving.Forums” or
“Monster.Timesheets”.
2.
Classes that will be used across
multiple applications under the same company segment should be named using the
root “<Company Segment>.”. For
example, “Monster.” or “MonsterMoving.”.
3.
Classes that will be used
across company segments should be named using “Global.”.
Any classes that need to follow
rules 2 or 3 must be approved by the .NET Architecture group (contact Chris
Bowen or Tim Weaver.)
The root should be followed by one or more
grouping names to indicate specific functionality and/or membership with other
classes. For example, “Monster.Resume”
or “Monster.Resume.Conversion”
Namespace Structure
Each “dot level” of a namespace should be
organized into a separate directory. For
example, “Monster.Resume” would have two directories, “\Monster” and
“\Monster\Resume”. In addition, each
level of a hierarchy should generally be built into a separate assembly
for ease of deployment as changes are made (e.g. “Monster.Resume.dll” and
“Monster.Resume.Conversion.dll”.) There
may be cases where it is not practical to divide namespaces into separate
assemblies, typically where circular references would result, but this should
be the exception, not the norm.
Each dot level of a namespace may be
dependent upon the classes in the shallower levels, but not the
reverse. For example, “Monster.Resume.Conversion”
may use classes found in “Monster.Resume”, but “Monster.Resume” classes should
never depend upon those contained in “Monster.Resume.Conversion”.
Namespace Aliasing
If referenced namespaces have an overlap
in, say, a class name, use an alias to distinguish them. The alias is typically the first letter of
each word (not just each dot level) in the namespace. Prefer to alias a custom (i.e. Monster)
namespace over a standard Framework one to aid readability. In other words, if “Monster.Useless.BadExamples”
had a class named “DataColumn”, it would be in conflict with System.Data.DataColumn. To resolve the ambiguity, alias the
“Monster.Useless.BadExamples” reference as:
using System.Data;
using MUBE = Monster.Useless.BadExamples;
...
MUBE.DataColumn something = new MUBE.DataColumn(); // Uses Monster.Useless.BadExamples
DataColumn builtInSomething = new DataColumn(); // Uses System.Data
...
MUBE.DataColumn something = new MUBE.DataColumn(); // Uses Monster.Useless.BadExamples
DataColumn builtInSomething = new DataColumn(); // Uses System.Data
Classes
Objects should be modeled after distinct
entities which can have abilities (methods)
and/or attributes (properties.) Classes should be named using nouns (in
Pascal case – words combined with first letter of each word capitalized)
descriptive of the entity. For example,
the System.Collections namespace has
classes with names such as ArrayList,
HashTable, and Stack, all descriptive of the capabilities of each object. Do not use the underscore character (“_”).
Classes should generally be organized into
separate files, one file per class, named <classname>.cs.
Collections
- Classes serving as collections should have names ending with
“Collection”. The general form when
aggregating an entity class is <entityname>Collection.
Example:
public class CustomerCollection {...} // Contains a collection of
Customer instances
Attributes
- Custom attributes must be named ending with “Attribute”.
Example:
public sealed class SampleAttribute : Attribute {...}
Interfaces
Interfaces should be named with a prefix of
“I” followed by nouns, noun phrases or adjectives describing behavior and, like
classes, in Pascal case. For example, IEnumerable and ICollection. Do not use
underscores.
Like classes, each interface should be
stored in a separate file named <interfacename>.cs. (e.g. “ICollection.cs”.)
Variables
Variable Names
A variable name should use camel casing
(all words combined without spaces, and the first letter of each word except
the first word are capitalized) and should be as long as necessary to describe
its purpose. (e.g.
“totalShippingAndHandling” and “userID”.)
·
Use the plural form of nouns
when used in collections or arrays (e.g. string[]
stateNames.)
·
See Abbreviations below for
further naming guidelines.
Variable Naming Exceptions
It is accepted practice to use single
letters (e.g. “i”, “j”, “k” …) for tightly-scoped variables, typically index or
counter variables in loops, unless readability would be improved by using a
longer, more descriptive name.
Often, an object instance may be named by
the combination of the first letters of each word (or accepted abbreviation) of
the class name. For example, a StringBuilder might be represented by
the variable “sb”. Others might include
“cn” for SqlConnection or “cmd” for SqlCommand objects. Multiple objects of the same type in the same
scope can be differentiated by appending a description to the root, for
example, “cnSecurity” and “cnResume”.
Class Fields and Properties
To help
enforce encapsulation, you should avoid exposing fields directly from a
class. Fields should be marked private
(or protected) and exposed outside the class via properties, allowing control
over how the variable is set. A private
field of a class should be named with a leading underscore (“_”) character. If it is necessary to expose that variable
publicly, use a property (get/set) with a Pascal
case name.
Example:
private int _partID = DEFAULT_PART_ID;
public int PartID
{
get {return _partID;}
set
{
if (value <
1)
{
throw new
ArgumentOutOfRangeException(“PartID”, value, “Must be greater than zero”);
}
_partID = value;
_partID = value;
}
}
Parameters
Variables used in parameter lists (such as
in constructors or methods) should use camel casing.
Example:
public string SomeMethod (int userID,
string userName, string password)
{
/// ...
}
{
/// ...
}
Methods
Follow the same rules as Variable naming
with the following additions:
- Use Pascal casing (all words combined without spaces and first
letter of each word capitalized)
- The name should be as long as necessary to describe its
purpose.
- Should generally be composed of verbs or verb phrases (e.g.
“CreateNameArray()”, “Persist()”,
“GetUserPreferences()”.)
Abbreviations
- Avoid the use of abbreviations in
general, preferring to spell names out fully (e.g. use “GetWindow” instead
of “GetWin” or “password” instead of “pwd” or “pass”.)
- Use abbreviations only if they are generally well-known by
developers (e.g. Guid, UI, Http)
- Use abbreviations to shorten references to longer phrases (e.g.
“UI” for “UserInterface”)
- If you must use abbreviations, ensure they are consistent
throughout the entire application.
- Abbreviations longer than two characters should use mixed or
lower case as appropriate in the current context (e.g.
customHttpException, httpModule, GetHttpResult) otherwise, use all upper
or lower case as appropriate (e.g. userID, idSource, ValidateCustomerID.)
Constants
Follow the same general rules as Variable
naming with the following exceptions:
- To aid readability and identification, the names of Constants
should be UPPER_CASE with underscores (_) between words.
Example:
private const string DEFAULT_EMAIL_ADDRESS = “nobody@monster.com”;
public const int LOGIN_TIMEOUT_SECONDS = 45;
public const int LOGIN_TIMEOUT_SECONDS = 45;
Enumerations
Follow the same general rules as Variable
naming with the following exceptions:
- Use Pascal case for enumeration
types and value names.
- Use abbreviations sparingly.
- Do not use an
Enum
suffix on enumeration type names. - Use a singular name for most enumeration types, but use a plural name for enumeration types that are bit
fields.
Example:
In,
Out,
Out,
InOut
}
Regarding
Hungarian Notation
It was common practice
before .NET to use the variable naming convention known as Hungarian notation
(e.g. arrstrStateNames,) but with .NET, the usefulness of Hungarian is
minimized.
From Inside C#, by Archer: “So why not simply continue using Hungarian
notation? Because
Hungarian notation is useful in situations where it's beneficial to know the
type, or scope, of a variable being used. However, […] all types in C# are
objects and based on the .NET System.Object class. Therefore, all
variables have a basic set of functionality and behavioral characteristics. For
this reason, the need for Hungarian notation is lessened in the .NET
environment.”
In addition, as all .NET
development is standardized on VisualStudio.net, identification of the type of
a given variable is merely a tool tip away.
The use of Hungarian
notation is to be avoided. This approach
is standardized in the .NET Framework
General Reference and numerous publications.
More Information
For more information on
naming conventions in the .NET environment, see “Naming Guidelines” in the .NET Framework General Reference (which
can be found through help in Visual Studio.NET.)
Comments
Post a Comment