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 PascalCase — not 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.