Skip to content

C# Coding Standards

Naming Conventions

General Guidelines

  • Use PascalCase for class names, method names, and public members
  • Use camelCase for local variables, parameters, class scoped fields
  • Prefix interface names with "I" (e.g., IDisposable)
  • Use meaningful and descriptive names that show core function

PascalCase when public, camelCase when private/internal as a general rule

Specific Rules

  1. Constants: Declare constants in PascalCase
  2. Example: const int MaxValue = 100;
  3. Class Names: Use a suffix that describes the class's purpose
  4. Example: ObjectHandler, DataProcessor
  5. Boolean Methods/Properties: Methods or properties returning booleans should usually have an "Is" or "Has" prefix
  6. Example: IsValid(), HasPermission
  7. Interfaces: Prefix interface names with "I"
  8. Example: IDisposable, IComparable

Case Conventions

  • Use PascalCase for:
  • ✅ Class names
  • ✅ Method names
  • ✅ Public members
  • Use camelCase for:
  • ✅ Local variables
  • ✅ Parameters
  • ✅ Class scoped fields

Constructors

Primary Constructor Syntax

  • Use primary constructor syntax when appropriate, especially for simple classes or record types.
  • This syntax promotes concise and readable code.
  • Example: public class Person(string name, int age)

Multiple Constructors:

  • When a class requires multiple constructors, consider using the primary constructor in combination with additional constructors.
  • Use constructor chaining to avoid code duplication.
  • Example:

    public class Employee(string name, int age, string department) : Person(name, age) { public Employee(string name, int age) : this(name, age, "General") { } }

Default Values

  • When using primary constructors, you can specify default values for parameters.
  • Example:

    public class Configuration(string environment = "Development", bool isDebug = false) { }

Readability

  • Use XML comments to document constructor parameters.

Complexity & Nesting

Nesting Limit

  • Avoid nesting loops and conditions generally.
  • When nesting is necessary, limit it to no more than 2 levels deep.

Flatten Conditionals

  • Use logical operators to combine conditions and reduce nesting.
  • Consider using switch expressions or pattern matching for complex conditionals.

Lambdas

  • Keep lambda expressions simple and avoid nesting within them.
  • If a lambda becomes complex, consider moving logic to a method.

Be Descriptive

  • To reduce cognitive load be descriptive with variable naming

Simplify Where Possible

Nesting can normally be avoided through refactoring. Example:

    // Before: Deeply nested
    public void ProcessData(List<Data> dataList)
    {
        foreach (var data in dataList)
        {
            if (data.IsValid)
            {
                if (data.NeedsProcessing)
                {
                    // Process data
                }
            }
        }
    }

    // After: Refactored to reduce nesting
    public void ProcessData(List<Data> dataList)
    {
        var validData = dataList.Where(d => d.IsValid && d.NeedsProcessing);
        foreach (var data in validData)
        {
            ProcessSingleDataItem(data);
        }
    }

    private void ProcessSingleDataItem(Data data)
    {
        // Process data
    }

Visibility & Access Modifiers

When determining the visibility of classes, interfaces, and other types, follow these guidelines:

Classes

  • Classes should generally be internal by default.
  • Make classes public only when they need to be accessible outside the assembly.
  • Exceptions:
  • Domain Models may be public when needed for cross-assembly data transfer.
  • Classes that need to be visible for .NET Core or other framework functionality.

Interfaces

  • Interfaces should be public by default.
  • This allows for better decoupling and enables dependency injection across assembly boundaries.

Visibility Hierarchy

  • Use the least permissive access modifier that still allows the code to function correctly.
  • Consider the following order of preference (from most to least restrictive):
  • private
  • internal
  • protected (for inheritance scenarios)
  • public

Method Parameters

When defining and using method parameters, follow these guidelines:

Parameter Order

  • When passing value types (e.g., booleans), always place these parameters first in the parameter list.
  • Follow with reference type parameters.

Parameter Formatting

  • For methods with multiple parameters:
  • Place each parameter on a separate line if the parameter list becomes too long for a single line.
  • Align parameters for readability.
  • Example:

    public void SomeMethod( bool isValid, int count, string name, ComplexType complexObject) { // Method body }

Consider Using Parameter Objects

  • If a method has many parameters, consider grouping related parameters into a parameter object.
  • This can improve readability and make the method signature more manageable.

Asynchronous Programming

When working with asynchronous code, adhere to these guidelines:

CancellationToken Usage

  • All asynchronous methods should accept a CancellationToken parameter.
  • This allows for proper cancellation of long-running operations.
  • Example: public async Task<Result> GetData(CancellationToken cancellationToken)

Cancellation Handling

  • When passing a CancellationToken, ensure the operation can be properly cancelled.
  • Check the token's status at appropriate intervals in long-running operations.
  • Async vs Sync Methods:
  • Prefer async methods where available.
  • Use synchronous methods only when async alternatives are not available or when performance requirements dictate their use.
  • Avoid Blocking Calls:
  • Do not use .Result or .Wait() on tasks, as this can lead to deadlocks.
  • Instead, use await to handle asynchronous operations.

Dependency Injection(DI)

Usage

  1. Avoid Direct Instantiation: Never use the new keyword to create an object, except for models.

  2. ❌ Don't: var service = new MyService();

  3. 👍 Okay: var user = new User { Name = "John Doe", Age = 30 };

  4. Instead Use Constructor Injection:

  5. Prefer primary constructor injection for required dependencies.
  6. This makes dependencies explicit and ensures they are available when the object is created.
  7. ✅ Do: public class ExampleController(IService service) : ControllerBase

Registration

  • Interface-based Registration:
  • Register objects using their interfaces, not concrete classes.
  • This promotes loose coupling and makes it easier to swap implementations.
  • 👍 Okay: `services.AddScoped();
  • Scoped Registrations:
  • Registrations should usually be scoped.
  • Use other lifetimes (Singleton, Transient) only when there's a specific reason to do so.

Testing

Follow this Class structure

  • Integration test class per API Endpoint
  • Unit test class for each testable class

Write Readable Tests

  • Use descriptive test names that explain the scenario being tested.
  • Employ the Arrange-Act-Assert pattern for clear test structure.
  • Use FluentAssertions for assertions.

Generate Test Data

  • Use the AutoFixture auto data pattern to generate data used inside tests. Where appropriate use the [CustomAutoData] attribute.
  • Use collection fixtures where context is shared between tests

Follow the Testing Strategy for test class content.

Comments

Required XML Comments

  • Use XML comments for all public interfaces, classes, methods, and properties.
  • This ensures that your public APIs are well-documented and can be easily understood by other developers.
  • Example:

    ///

    /// Represents a user in the system. /// public interface IUser { /// /// Gets or sets the user's unique identifier. /// int Id { get; set; }

      /// <summary>
      /// Gets or sets the user's display name.
      /// </summary>
      string Name { get; set; }
    

    }

General Practice

Consider Refactoring

  • Writing self-explanatory code using meaningful variable and method names can replace comments.
  • If a piece of code is complex enough to require explanation, consider refactoring it into smaller, more descriptive methods.

When to Comment?

  • Add comments when the reason for the code existing cannot be immediately clear from its structure and naming.
  • ✅ Comments should explain why something is done
  • ❌ Don't comment what is being done.

Maintain Comments

  • Ensure that comments are kept up-to-date when the code changes.
  • Outdated comments can be misleading and cause confusion.

Use TODO Comments Sparingly

  • Use TODO comments to mark temporary code or reminder for future changes.
  • Always include a brief explanation of what needs to be done.