Coding Guidelines
Arrays
AVOID unsafe array covariance. Instead, CONSIDER converting the array to the read-only interface IEnumerable<T>, which can be safely converted via covariant conversions.
DO use parameter arrays when a method can handle any number—including zero—of additional arguments.
CONSIDER checking the array length before indexing into an array rather than assuming the length.
CONSIDER using the index from end operator (^) rather than Length - 1 with C# 8.0 or higher.
Assemblies
DO apply AssemblyVersionAttribute to assemblies with public types.
CONSIDER applying AssemblyFileVersionAttribute and AssemblyCopyrightAttribute to provide additional information about the assembly.
DO apply the following information assembly attributes: System.Reflection.AssemblyCompanyAttribute, System.Reflection.AssemblyCopyrightAttribute, System.Reflection.AssemblyDescriptionAttribute, and System.Reflection.AssemblyProductAttribute.
DO name custom attribute classes with the suffix Attribute.
DO provide get-only properties (without public setters) on attributes with required property values.
DO provide constructor parameters to initialize properties on attributes with required properties. Each parameter should have the same name (albeit with different casing) as the corresponding property.
AVOID providing constructor parameters to initialize attribute properties corresponding to the optional arguments (and, therefore, avoid overloading custom attribute constructors).
DO apply the AttributeUsageAttribute class to custom attributes.
DO NOT explicitly pass arguments for Caller* attribute decorated parameters.
DO use non-nullable string for the data type of Caller* attribute decorated string parameters.
DO assign null! for the default value of Caller* attribute decorated string parameters.
Branches
CONSIDER using an if-else statement instead of an overly complicated conditional expression.
CONSIDER refactoring the method to make the control flow easier to understand if you find yourself writing for loops with complex conditionals and multiple loop variables.
DO NOT use continue as the jump statement that exits a switch section. This is legal when the switch is inside a loop, but it is easy to become confused about the meaning of break in a later switch section.
AVOID using goto.
DO use parallel loops when the computations performed can be easily split up into many mutually independent processor-bound computations that can be executed in any order on any thread.
Classes
AVOID placing more than one class in a single source file.
DO name the source file with the name of the public type it contains.
AVOID publicly exposed nested types. The only exception is if the declaration of such a type is unlikely or pertains to an advanced customization scenario.
DO use records, where possible, if you want equality based on data rather than identity.
Comments
DO NOT use comments unless they describe something that is not obvious to someone other than the developer who wrote the code.
DO favor writing clearer code over entering comments to clarify a complicated algorithm.
Conversions
DO NOT provide an implicit conversion operator if the conversion is lossy.
DO NOT throw exceptions from implicit conversions.
Enums
DO NOT use the enum type name as part of the values name.
DO use an enum type name that is singular unless the enum is a flag.
AVOID direct enum/string conversions where the string must be localized into the user’s language.
DO use the FlagsAttribute to mark enums that contain flag values.
DO provide a None value equal to 0 for all enums.
AVOID creating flag enums where the zero value has a meaning other than “no flags are set.”
CONSIDER providing special values for commonly used combinations of flags.
DO NOT include “sentinel” values (such as a value called Maximum); such values can be confusing to the user.
DO use powers of 2 to ensure that all flag combinations are represented uniquely.
Equality
DO ensure that custom comparison logic produces a consistent “total order.”
Exceptions
AVOID exception reporting or logging lower in the call stack.
DO NOT over-catch. Exceptions should be allowed to propagate up the call stack unless it is clearly understood how to programmatically address those errors lower in the stack.
CONSIDER catching a specific exception when you understand why it was thrown in a given context and can respond to the failure programmatically.
AVOID catching System.Exception or System.SystemException except in top-level exception handlers that perform final cleanup operations before rethrowing the exception.
DO use throw; rather than throw <exception object> inside a catch block.
DO use exception filters to avoid rethrowing an exception from within a catch block.
DO use caution when rethrowing different exceptions.
AVOID throwing exceptions from exception filters.
AVOID exception filters with logic that might implicitly change over time.
AVOID deep exception hierarchies.
DO NOT create a new exception type if the exception would not be handled differently than an existing CLR exception. Throw the existing framework exception instead.
DO create a new exception type to communicate a unique program error that cannot be communicated using an existing CLR exception and that can be programmatically handled in a different way than any other existing CLR exception type.
DO provide a parameterless constructor on all custom exception types. Also provide constructors that take a message and an inner exception.
DO name exception classes with the “Exception” suffix.
DO make exceptions runtime-serializable.
CONSIDER providing exception properties for programmatic access to extra information relevant to the exception.
CONSIDER wrapping specific exceptions thrown from the lower layer in a more appropriate exception if the lower-layer exception does not make sense in the context of the higher-layer operation.
DO specify the inner exception when wrapping exceptions.
DO target developers as the audience for exceptions, identifying both the problem and the mechanism to resolve it, where possible.
AVOID explicitly throwing exceptions from finally blocks. (Implicitly thrown exceptions resulting from method calls are acceptable.)
DO favor try/finally and avoid using try/catch for cleanup code.
DO throw exceptions that describe which exceptional circumstance occurred and, if possible, how to prevent it.
DO prefer using an empty throw when catching and rethrowing an exception, to preserve the call stack.
DO report execution failures by throwing exceptions rather than returning error codes.
DO NOT have public members that return exceptions as return values or an out parameter. Throw exceptions to indicate errors; do not use them as return values to indicate errors.
AVOID catching and logging an exception before rethrowing it. Instead, allow the exception to escape until it can be handled appropriately.
DO verify that non-null reference types parameters are not null and throw an ArgumentNullException when they are.
DO use ArgumentException.ThrowIfNull() to verify values are null in .NET 7.0 or later.
DO NOT use exceptions for handling normal, expected conditions; use them for exceptional, unexpected conditions.
DO create public managed wrappers around unmanaged methods that use the conventions of managed code, such as structured exception handling.
Fields
DO use constant fields for values that will never change.
AVOID constant fields for values that will change over time.
Files
DO provide XML comments on public APIs when they provide more context than the API signature alone. This includes member descriptions, parameter descriptions, and examples of calling the API.
Increment/decrement
AVOID confusing usage of the increment and decrement operators.
DO be cautious when porting code between C, C++, and C# that uses increment and decrement operators; C and C++ implementations need not follow the same rules as C#.
Interfaces
DO use PascalCasing and an “I” prefix for interface names.
CONSIDER defining an interface if you need to support its functionality on types that already inherit from some other type.
AVOID using “marker” interfaces with no members; use attributes instead.
Methods
AVOID the anonymous method syntax in new code; prefer the more compact lambda expression syntax.
AVOID frivolously defining extension methods, especially on types you don’t own.
CONSIDER using extension methods or an additional interface in place of default interface members when adding methods to a published interface.
DO use extension methods when the interface providing the polymorphic behavior is not under your control.
DO use query expression syntax to make queries easier to read, particularly if they involve complex from, let, join, or group clauses.
CONSIDER using the standard query operators (method call form) if the query involves operations that do not have a query expression syntax, such as Count(), TakeWhile(), or Distinct().
DO give methods names that are verbs or verb phrases.
DO use System.Linq.Enumerable.Any() rather than calling patents.Count() when checking whether there are more than zero items.
DO use a collection’s Count property (if available) instead of calling the System.Linq.Enumerable.Count() method.
DO NOT call an OrderBy() following a prior OrderBy() method call. Use ThenBy() to sequence items by more than one value.
Miscellaneous
CONSIDER omitting the types from lambda formal parameter lists when the types are obvious to the reader or when they are an insignificant detail.
DO use uppercase literal suffixes (e.g., 1.618033988749895M).
DO rely on System.Console.WriteLine() and System.Environment.NewLine rather than \n to accommodate Windows-specific operating system idiosyncrasies with the same code that runs on Linux and macOS.
AVOID binary floating-point types when exact decimal arithmetic is required; use the decimal floating-point type instead.
AVOID using equality conditionals with binary floating-point types. Either subtract the two values and see if their difference is less than a tolerance or use the decimal type.
DO NOT use a constant for any value that can possibly change over time. The value of pi and the number of protons in an atom of gold are constants; the price of gold, the name of your company, and the version number of your program can change.
AVOID omitting braces, except for the simplest of single-line if statements.
DO use the for loop when the number of loop iterations is known in advance and the “counter” that gives the number of iterations executed is needed in the loop.
DO use the while loop when the number of loop iterations is not known in advance and a counter is not needed.
DO favor clarity over brevity when naming identifiers.
DO NOT use abbreviations or contractions within identifier names.
DO NOT use any acronyms unless they are widely accepted, in which case use them consistently.
DO capitalize both characters in two-character acronyms, except for the first word of a camelCased identifier.
DO capitalize only the first character in acronyms with three or more characters, except for the first word of a camelCased identifier.
DO NOT capitalize any of the characters in acronyms at the beginning of a camelCased identifier.
DO NOT use Hungarian notation (that is, do not encode the type of a variable in its name).
AVOID target-typed new expressions when the data type of the constructor is not obvious.
CONSIDER use target-typed new expressions when the data type of the constructor is obvious.
DO provide sensible defaults for all properties, ensuring that defaults do not result in a security hole or significantly inefficient code.
DO allow properties to be set in any order, even if this results in a temporarily invalid object state.
DO use the same name for constructor parameters (camelCase) and properties (PascalCase) if the constructor parameters are used to simply set the property.
DO provide constructor optional parameters or constructor overloads that initialize properties with good defaults.
DO NOT use constructor parameters to initialize required properties; instead, rely on object initializer–specified values.
DO NOT use the SetRequiredParameters attribute unless all required parameters are assigned valid values during construction.
CONSIDER having a default constructor only on types with required parameters, relying on the object initializer to set both required and non-required members.
AVOID adding required members to released types to avoid breaking the compile on existing code.
AVOID required members where the default value of the type is valid.
CONSIDER initializing static fields inline rather than explicitly using static constructors or declaration assigned values.
DO favor read-only automatically implemented properties over read-only fields.
DO check that the value of a delegate is not null before invoking it.
DO use the null-conditional operator prior to calling Invoke() starting in C# 6.0.
DO check that the value of a delegate is not null before invoking it (possibly by using the null-conditional operator in C# 6.0).
DO pass the instance of the class as the value of the sender for nonstatic events.
DO pass null as the sender for static events.
DO NOT pass null as the value of the eventArgs argument.
DO use System.EventArgs or a type that derives from System.EventArgs for a TEventArgs type.
CONSIDER using a subclass of System.EventArgs as the event argument type (TEventArgs) unless you are sure the event will never need to carry any data.
DO NOT throw exceptions from within the implementation of operator overloading.
DO implement the dispose pattern on objects with resources that are scarce or expensive.
DO implement IDisposable to support possible deterministic finalization on classes with finalizers.
DO implement finalizer methods only on objects with resources that don't have finalizers but still require cleanup.
DO refactor a finalization method to call the same code as IDisposable, perhaps simply by calling the Dispose() method.
DO NOT throw exceptions from finalizer methods.
CONSIDER registering the finalization code with the AppDomain.ProcessExit to increase the probability that resource cleanup will execute before the process exits.
DO unregister any AppDomain.ProcessExit events during dispose.
DO call System.GC.SuppressFinalize() from Dispose() to avoid repeating resource cleanup and delaying garbage collection on an object.
DO ensure that Dispose() is idempotent (it should be possible to call Dispose() multiple times).
DO keep Dispose() simple, focusing on the resource cleanup required by finalization.
AVOID calling Dispose() on owned objects that have a finalizer. Instead, rely on the finalization queue to clean up the instance.
AVOID referencing other objects that are not being finalized during finalization.
DO invoke a base class’s Dispose() method when overriding Dispose().
CONSIDER ensuring that an object becomes unusable after Dispose() is called. After an object has been disposed, methods other than Dispose() (which could potentially be called multiple times) should throw an ObjectDisposedException.
DO implement IDisposable on types that own disposable fields (or properties) and dispose of those instances.
DO invoke a base class’s Dispose() method from the Dispose(bool disposing) method if one exists.
DO NOT add members without a default implementation to a published interface.
DO treat parameter names as part of the API, and avoid changing the names if version compatibility between APIs is important.
AVOID general catch blocks and replace them with a catch of System.Exception.
AVOID catching exceptions for which the appropriate action is unknown. It is better to let an exception go unhandled than to handle it incorrectly.
DO use nameof(value) (which resolves to "value") for the paramName argument when creating ArgumentException() or ArgumentNullException() type exceptions. (value is the implicit name of the parameter on property setters.)
DO NOT make any unwarranted assumptions about the order in which elements of a collection will be enumerated. If the collection is not documented as enumerating its elements in a particular order, it is not guaranteed to produce elements in any particular order.
DO NOT represent an empty collection with a null reference.
CONSIDER using the Enumerable.Empty<T>() method instead.
DO NOT define a struct unless it logically represents a single value, consumes 16 bytes or less of storage, is immutable, and is infrequently boxed.
CONSIDER using the default 32-bit integer type as the underlying type of an enum. Use a smaller type only if you must do so for interoperability; use a larger type only if you are creating a flags enum with more than 32 flags.
CONSIDER adding new members to existing enums, but keep in mind the compatibility risk.
AVOID creating enums that represent an “incomplete” set of values, such as product version numbers.
AVOID creating “reserved for future use” values in an enum.
AVOID enums that contain a single value.
DO provide a value of 0 (none) for simple enums, knowing that 0 will be the default value when no explicit initialization is provided.
DO use camelCasing for variable declarations using tuple syntax.
CONSIDER using PascalCasing for all tuple item names.
DO NOT unnecessarily replicate existing managed classes that already perform the function of the unmanaged API.
DO declare extern methods as private or internal.
DO provide public wrapper methods that use managed conventions such as structured exception handling, use of enums for special values, and so on.
DO simplify the wrapper methods by choosing default values for unnecessary parameters.
DO use the SetLastErrorAttribute on Windows to turn APIs that use SetLastError error codes into methods that throw Win32Exception.
DO extend SafeHandle or implement IDisposable and create a finalizer to ensure that unmanaged resources can be cleaned up effectively.
DO use delegate types that match the signature of the desired method when an unmanaged API requires a function pointer.
DO use ref parameters rather than pointer types when possible.
Namespaces
DO use file-scoped namespaces (C# 10.0 or later).
DO prefix namespace names with a company name to prevent namespaces from different companies having the same name.
DO use a stable, version-independent product name at the second level of a namespace name.
DO NOT define types without placing them into a namespace.
CONSIDER creating a folder structure that matches the namespace hierarchy.
DO use PascalCasing for namespace names.
CONSIDER organizing the directory hierarchy for source code files to match the namespace hierarchy.
Parameters
DO place multiple generic semantically equivalent classes into a single file if they differ only by the number of generic parameters.
DO use camelCasing for parameter names.
DO provide good defaults for all parameters where possible.
DO provide simple method overloads that have a small number of required parameters.
CONSIDER organizing overloads from the simplest to the most complex.
Properties
DO use properties for simple access to simple data with simple computations.
AVOID throwing exceptions from property getters.
DO preserve the original property value if the property throws an exception.
CONSIDER using the same casing on a property’s backing field as that used in the property, distinguishing the backing field with an “_” prefix.
DO name properties using a noun, noun phrase, or adjective.
CONSIDER giving a property the same name as its type.
AVOID naming fields with camelCase.
DO favor prefixing Boolean properties with “Is,” “Can,” or “Has,” when that practice adds value.
DO declare all instance fields as private (and expose them via a property).
DO name properties with PascalCase.
DO favor automatically implemented properties over fields.
DO favor automatically implemented properties over using fully expanded ones if there is no additional implementation logic.
AVOID accessing the backing field of a property outside the property, even from within the containing class.
DO create read-only properties if the property value should not be changed.
DO create read-only automatically implemented properties, rather than read-only properties with a backing field if the property value should not be changed.
DO apply appropriate accessibility modifiers on implementations of getters and setters on all properties.
DO NOT provide set-only properties or properties with the setter having broader accessibility than the getter.
DO implement non-nullable read/write reference fully implemented properties with a nullable backing field, a null-forgiveness operator when returning the field from the getter, and non-null validation in the property setter.
DO assign non-nullable reference type properties before instantiation completes.
DO implement non-nullable reference type automatically implemented properties as read-only.
DO use a nullable check for all reference type properties and fields that are not initialized before instantiation completes.
DO use PascalCase for the positional parameters of the record (C# 9.0).
DO define all reference type positional parameters as nullable if not providing a custom property implementation that checks for null.
DO implement custom non-nullable properties for all non-nullable positional parameters.
Strings
DO favor composite formatting over use of the addition operator for concatenating strings when localization is a possibility.
Structs
DO use record struct when declaring a struct (C# 10.0).
DO use record class (C# 10.0) for clarity, rather than the abbreviated record-only syntax.
DO ensure that the default value of a struct is valid; encapsulation cannot prevent obtaining the default “all zero” value of a struct.
DO NOT rely on either default constructors or member initialization at declaration to run on a value type.
Synchronization
AVOID using the MethodImplAttribute for synchronization.
Tasks
DO cancel unfinished tasks rather than allowing them to run during application shutdown.
DO inform the task factory that a newly created task is likely to be long-running so that it can manage it appropriately.
DO use TaskCreationOptions.LongRunning sparingly.
Threads
AVOID calling Thread.Sleep() in production code.
DO use tasks and related APIs in favor of System.Theading classes such as Thread and ThreadPool.
AVOID locking on this, System.Type, or a string.
DO declare a separate, read-only synchronization variable of type object for the synchronization target.
DO NOT request exclusive ownership of the same two or more synchronization targets in different orders.
DO ensure that code that concurrently holds multiple locks always acquires them in the same order.
DO encapsulate mutable static data in public APIs with synchronization logic.
AVOID synchronization on simple reading or writing of values no bigger than a native (pointer-size) integer, as such operations are automatically atomic.
ToString()
DO override ToString() whenever useful developer-oriented diagnostic strings can be returned.
CONSIDER trying to keep the string returned from ToString() short.
DO NOT return an empty string or null from ToString().
DO NOT throw exceptions or make observable side effects (change the object state) from ToString().
DO provide an overloaded ToString(string format) or implement IFormattable if the return value requires formatting or is culture-sensitive (e.g., DateTime).
CONSIDER returning a unique string from ToString() so as to identify the object instance.
Types
CONSIDER whether the readability benefit of defining your own delegate type outweighs the convenience of using a predefined generic delegate type.
DO use the C# keyword rather than the unqualified name when specifying a data type (e.g., string rather than String).
DO favor consistency rather than variety within your code.
DO name classes with nouns or noun phrases.
DO use PascalCasing for all class names.
DO choose meaningful names for type parameters and prefix the name with T.
CONSIDER indicating a constraint in the name of a type parameter.
AVOID shadowing a type parameter of an outer type with an identically named type parameter of a nested type.
DO throw an ArgumentException or one of its subtypes if bad arguments are passed to a member. Prefer the most derived exception type (e.g., ArgumentNullException), if applicable.
DO NOT throw a System.SystemException or an exception type that derives from it.
DO NOT throw a System.Exception, System.NullReferenceException, or System.ApplicationException.
CONSIDER terminating the process by calling System.Environment.FailFast() if the program encounters a scenario where it is unsafe to continue execution.
DO use nameof for the paramName argument passed into argument exception types that take such a parameter. Examples of such exceptions include ArgumentException, ArgumentOutOfRangeException, and ArgumentNullException.
DO use the readonly modifier on a struct definition, making value types immutable.
DO use read-only or init-only setter automatically implemented properties rather than fields within structs.
Variables
DO use camelCasing for local variable names.
AVOID using implicitly typed local variables (var) unless the data type of the assigned value is obvious.