Skip to content

C# – Naming Conventions

C# naming conventions are defined by Microsoft's official .NET documentation, the .NET Runtime team's coding style, and the ASP.NET Core project. Following them ensures Cygnus Dynamics C# code is consistent, immediately readable, and integrates cleanly with the wider .NET ecosystem.

Enforcement

All naming rules are enforced via .editorconfig and Roslyn analysers configured in every project. Visual Studio and Rider highlight violations inline. The CI build fails on any naming rule violation at warning severity or higher.

Quick Reference

Identifier Convention Example
Namespace PascalCase CygnusDynamics.Orders.Service
Class PascalCase (noun) OrderService, PaymentProcessor
Interface PascalCase prefixed I IOrderRepository, IPaymentGateway
Struct PascalCase Money, Coordinate
Enum type PascalCase singular OrderStatus, UserRole
Enum values PascalCase OrderStatus.Pending
Record PascalCase OrderSummary, CreateOrderRequest
Delegate PascalCase OrderCreatedHandler
Event PascalCase OrderCreated, PaymentFailed
EventArgs subclass PascalCase + EventArgs OrderCreatedEventArgs
Method PascalCase (verb) GetOrderById(), ProcessPayment()
Async method PascalCase + Async suffix GetOrderAsync(), SaveAsync()
Property PascalCase TotalAmount, IsActive
Public field PascalCase (use sparingly) IsValid
Constant PascalCase MaxRetryCount, DefaultCurrency
Private field _camelCase _orderRepository, _logger
Static private field s_camelCase s_instance, s_cache
Thread-static field t_camelCase t_currentContext
Local variable camelCase orderTotal, isEligible
Parameter camelCase orderId, cancellationToken
Type parameter T prefix + PascalCase T, TEntity, TResult
Test class <ClassName>Tests OrderServiceTests
Test method Method_Condition_ExpectedResult GetOrderAsync_WhenNotFound_Throws

Namespaces

Namespaces use PascalCase and follow the structure CompanyName.ProductOrDomain.Feature:

// ✅ Correct — hierarchical, dot-separated PascalCase
namespace CygnusDynamics.Orders.Service;
namespace CygnusDynamics.Payments.Gateway;
namespace CygnusDynamics.Users.Repository;
namespace CygnusDynamics.Common.Exceptions;

// ❌ Incorrect
namespace cygnusdynamics.orders;     // lowercase
namespace CygnusDynamics_Orders;     // underscores
namespace CD.Ord.Svc;               // abbreviations

Use file-scoped namespace declarations (C# 10+) — one namespace per file, no wrapping braces:

// ✅ File-scoped — cleaner, less nesting
namespace CygnusDynamics.Orders.Service;

public class OrderService { }

// ❌ Block-scoped — adds an entire level of indentation for no benefit
namespace CygnusDynamics.Orders.Service
{
    public class OrderService { }
}

Never name a class the same as its containing namespace

Orders.Order creates ambiguity in fully qualified names and confuses tooling. If the class and namespace would have the same name, make the namespace more specific: Orders.Domain.Order or Orders.Models.Order.

Classes

Class names are PascalCase nouns or noun phrases describing what the class is, not what it does.

// ✅ PascalCase nouns — clear purpose
public class OrderService { }
public class PaymentProcessor { }
public class UserProfileRepository { }
public class HttpRequestHandler { }    // HTTP acronym stays uppercase

// ❌ Incorrect
public class orderService { }          // lowercase start
public class ProcessOrders { }         // verb phrase — use a noun
public class OrdSvc { }               // abbreviation
public class Manager { }              // too vague

Common class naming patterns at Cygnus Dynamics:

Class Type Pattern Example
Service <Domain>Service OrderService
Repository <Domain>Repository UserRepository
Controller <Domain>Controller PaymentController
Request DTO <Action><Domain>Request CreateOrderRequest
Response DTO <Action><Domain>Response CreateOrderResponse
Exception <Description>Exception OrderNotFoundException
Domain event <Domain><Action>Event OrderCreatedEvent
Integration event <Domain><Action>IntegrationEvent OrderShippedIntegrationEvent
Configuration <Domain>Options DatabaseOptions, StripeOptions
Extension methods <Type>Extensions StringExtensions, QueryableExtensions
Abstract base <Domain>Base RepositoryBase<T>
Attribute <Description>Attribute RequiresAuthAttribute
Background service <Domain>BackgroundService EmailDispatchBackgroundService
Middleware <Description>Middleware RequestLoggingMiddleware
Validator <Domain>Validator CreateOrderRequestValidator

Interfaces

Interface names are PascalCase prefixed with I. The I prefix is mandatory — it is the primary C# convention used throughout the entire .NET ecosystem.

// ✅ I-prefix + PascalCase
public interface IOrderRepository { }
public interface IPaymentGateway { }
public interface INotificationSender { }
public interface IWorkerQueue { }

// ❌ Missing I prefix
public interface OrderRepository { }   // indistinguishable from a class
public interface IorderRepository { }  // lowercase after I

Methods

Method names are PascalCase verbs describing the action performed.

// ✅ PascalCase verbs
public Order GetOrderById(int orderId) { }
public void ProcessPayment(Payment payment) { }
public bool ValidateEmailAddress(string email) { }
public IEnumerable<Order> FindOrdersByStatus(OrderStatus status) { }

// ❌ Incorrect
public Order orderById(int id) { }     // camelCase — wrong
public Order order(int id) { }        // noun — not a verb
public void do_processing() { }       // underscore not used in C#

Async Method Naming

All methods returning Task or Task<T> must carry the Async suffix:

// ✅ Async suffix — immediately visible at every call site
public async Task<Order> GetOrderAsync(int orderId) { }
public async Task SendConfirmationEmailAsync(Order order) { }
public async Task<bool> ValidatePaymentAsync(Payment payment) { }

// ❌ Missing Async suffix — callers may forget to await it
public async Task<Order> GetOrder(int orderId) { }

Boolean-returning methods use prefixes that form a natural question:

// ✅ Reads naturally in conditions
public bool IsOrderEligibleForDiscount(Order order) { }
public bool HasActiveSubscription(User user) { }
public bool CanProcessRefund(Order order) { }

if (IsOrderEligibleForDiscount(order)) { ApplyDiscount(order); }

Properties and Fields

Properties

Properties are PascalCase. Prefer properties over public fields for all public state.

// ✅ PascalCase properties
public string FirstName { get; set; }
public decimal TotalAmount { get; private set; }
public bool IsActive { get; init; }
public IReadOnlyList<OrderItem> Items { get; }
public OrderStatus Status { get; private set; }

Private Fields

Private and internal fields use _camelCase — the underscore prefix is the .NET Runtime convention used by ASP.NET Core, Entity Framework, and the entire .NET OSS ecosystem.

// ✅ _camelCase for private instance fields
public class OrderService
{
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger<OrderService> _logger;
    private readonly IPaymentService _paymentService;
    private decimal _runningTotal;
}

The _ prefix pays off in IDEs

In Visual Studio or Rider, typing _ inside a method shows all instance-scoped members via IntelliSense, making it immediately clear whether you are accessing instance state or a local variable.

Static and Thread-Static Fields

public class DataService
{
    // Private static — s_ prefix
    private static readonly IWorkerQueue s_workerQueue;
    private static int s_instanceCount;

    // Thread-static — t_ prefix
    [ThreadStatic]
    private static TimeSpan t_requestDuration;
}

Constants

Constants use PascalCasenot UPPER_SNAKE_CASE. This is a key difference from Java, Python, and JavaScript.

// ✅ PascalCase constants — Microsoft's .NET convention
public const int MaxRetryCount = 3;
public const string DefaultCurrencyCode = "USD";
private const int BufferSize = 4096;

// ✅ static readonly — same PascalCase
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
public static readonly ImmutableList<string> SupportedCurrencies =
    ImmutableList.Create("USD", "EUR", "GBP");

// ❌ UPPER_SNAKE_CASE — not C# convention
public const int MAX_RETRY_COUNT = 3;   // C/Java/Python convention — avoid in C#

Events and Delegates

Events use PascalCase verb phrases in past tense (domain events) or present tense (UI/notifications). EventHandler-style delegates follow the EventArgs pattern:

// ✅ Domain events — PascalCase past tense
public event EventHandler<OrderCreatedEventArgs>? OrderCreated;
public event EventHandler<PaymentFailedEventArgs>? PaymentFailed;

// ✅ EventArgs subclass — class name + EventArgs suffix
public class OrderCreatedEventArgs : EventArgs
{
    public int OrderId { get; init; }
    public int UserId { get; init; }
    public decimal Total { get; init; }
    public DateTimeOffset CreatedAt { get; init; }
}

// ✅ Raising an event — null-check with ?.Invoke
protected virtual void OnOrderCreated(OrderCreatedEventArgs e)
{
    OrderCreated?.Invoke(this, e);
}

// ✅ Custom delegate — only when Func<>/Action<> is insufficient
public delegate Task OrderStatusChangedHandler(Order order, OrderStatus previous, CancellationToken ct);

// ✅ Use Action<>/Func<> for most delegate scenarios
private readonly Action<string> _logMessage;
private readonly Func<int, CancellationToken, Task<Order>> _fetchOrder;

Enums

Enum type names are PascalCase singular nouns. Use plural nouns for [Flags] enums.

// ✅ Singular for standard enum
public enum OrderStatus
{
    Pending,
    Confirmed,
    Shipped,
    Delivered,
    Cancelled,
}

// ✅ Plural for [Flags] enum
[Flags]
public enum FilePermissions
{
    None      = 0,
    Read      = 1,
    Write     = 2,
    Execute   = 4,
    ReadWrite = Read | Write,
}

// ❌ Never prefix enum values with the type name — redundant
public enum OrderStatus
{
    OrderStatusPending,     // OrderStatus.OrderStatusPending — very noisy
    OrderStatusConfirmed,
}

Records

Record primary constructor parameters use PascalCase (they become public properties). Regular class/struct primary constructor parameters use camelCase:

// ✅ Record — parameters PascalCase (they become public properties)
public record CreateOrderRequest(
    int UserId,
    string Currency,
    IReadOnlyList<OrderItemRequest> Items);

public record OrderSummary(
    int OrderId,
    decimal Total,
    OrderStatus Status,
    DateTimeOffset CreatedAt);

// ✅ Class with primary constructor — parameters camelCase
public class OrderService(IOrderRepository orderRepository, ILogger<OrderService> logger)
{
    private readonly IOrderRepository _orderRepository = orderRepository;
    private readonly ILogger<OrderService> _logger = logger;
}

Type Parameters

Generic type parameters use T as a single-letter name for simple generics, or T prefix + PascalCase for descriptive names:

// ✅ Single T for simple generic
public class Repository<T> where T : class { }
public T Execute<T>(Func<T> action) { }

// ✅ Descriptive T-prefix for complex generics
public class Converter<TSource, TTarget> { }
public class EventHandler<TEvent> where TEvent : IDomainEvent { }
public interface IRepository<TEntity, TKey> where TEntity : IEntity<TKey> { }

Special Naming Rules

Attributes

Attribute class names end with Attribute. When applying them, the suffix is omitted by convention:

// Declaration — full name with Attribute suffix
public class RequiresAuthAttribute : Attribute { }
public class ValidateModelAttribute : ActionFilterAttribute { }

// Usage — suffix omitted
[RequiresAuth]
[ValidateModel]
public IActionResult CreateOrder([FromBody] CreateOrderRequest request) { }

Acronyms

Two-letter acronyms stay fully uppercase. Acronyms of three or more letters use PascalCase:

// ✅ Two-letter acronyms — both letters uppercase
public string UserId { get; set; }      // ID stays uppercase
public Uri ApiUrl { get; set; }         // UI, IO, DB stay uppercase

// ✅ Longer acronyms — PascalCase
public string ToJson() { }             // Json, not JSON
public void ParseXml(string input) { } // Xml, not XML

Test Classes and Methods

Test class names mirror the class under test with a Tests suffix. Test method names follow the Method_WhenCondition_ExpectedResult pattern:

// ✅ Test class name
public class OrderServiceTests { }
public class PaymentProcessorTests { }
public class UserRepositoryIntegrationTests { }   // Integration suffix for integration tests

// ✅ Test method names
[Fact]
public async Task GetOrderAsync_WhenOrderExists_ReturnsOrder() { }

[Fact]
public async Task GetOrderAsync_WhenOrderNotFound_ThrowsOrderNotFoundException() { }

[Fact]
public async Task CreateOrderAsync_WhenCartIsEmpty_ThrowsArgumentException() { }

[Theory]
[InlineData(0)]
[InlineData(-1)]
public async Task GetOrderAsync_WhenIdIsInvalid_ThrowsArgumentOutOfRangeException(int id) { }

// ❌ Vague or unclear test names
[Fact]
public async Task Test1() { }

[Fact]
public async Task GetOrder_Works() { }

File Naming

File names must match class names

Each file must contain exactly one primary type, and the file name must match the class name exactly: OrderService.cs contains OrderService. Rider and Visual Studio will warn when these do not match.