AlgoMaster Logo

Naming Conventions

Last Updated: May 17, 2026

9 min read

Names are the first thing anyone sees when they read your code, and good names cut down on the back-and-forth between reading and thinking. C# has both hard rules about what's a legal identifier and softer style conventions that Microsoft, Roslyn, and the wider .NET community treat as the default. This lesson covers both layers, with examples from an e-commerce app so the patterns stick.

The Identifier Rules

Before any conventions, the compiler has its own set of rules about what counts as a valid identifier. An identifier is the name you give to a variable, method, class, field, parameter, or any other named thing in your code. The rules are short:

  • The first character must be a letter or an underscore (_).
  • After the first character, you can use letters, digits, or underscores.
  • Identifiers are case-sensitive. customer and Customer are two different names.
  • You can't use a C# reserved keyword (int, class, return, if, and so on) as an identifier.
  • Unicode letters are allowed, but English ASCII is the strong preference in real codebases.

Here's what legal and illegal identifiers look like:

There's one escape hatch for the keyword rule. Prefixing an identifier with @ tells the compiler to treat the keyword as a plain name:

This exists mostly so C# code can interoperate with libraries written in other .NET languages that have different keyword sets. In day-to-day C#, you should pick a different name instead of leaning on @. The escape hatch is there, but reaching for it makes code harder to read.

PascalCase, camelCase, and _camelCase

C# has three casing styles you'll see everywhere, and each one has a specific job. Mixing them up is the most common style mistake newcomers make.

PascalCase capitalizes the first letter of every word, including the first one: CustomerOrder, CalculateTotal, IsActive. It's used for everything visible from outside the class. Types, methods, public properties, public fields, events, enum members, namespaces, and constants all use PascalCase.

camelCase capitalizes the first letter of every word except the first: customerName, totalAmount, itemCount. It's used for things that live inside a method: local variables and method parameters.

`_camelCase` is camelCase with a leading underscore: _items, _logger, _dbContext. It's the conventional name for private fields. The underscore signals "this is internal state of the class, not a local variable."

Here's the same class showing all three in one place:

A few things to notice. The constructor parameter customerName is camelCase and shares its lowercase form with the property CustomerName, which is PascalCase. C# is case-sensitive, so the two are distinct, and the assignment CustomerName = customerName is unambiguous. The private field _items is _camelCase, while the public property ItemCount is PascalCase.

There's a less common variant called `s_camelCase` for private static fields: s_cache, s_instance. You'll see this in the .NET runtime source and some large codebases (it's the Roslyn convention), but most application code just uses _camelCase for both instance and static private fields. Pick one style per project and stick with it.

Choosing a Casing Style

When you're naming something new, walk down a short decision tree. Is it visible outside the type? Then PascalCase. Is it a local variable or parameter? Then camelCase. Is it a private field? Then _camelCase. Is it an interface? Then I followed by PascalCase. Is it a generic type parameter? Then T followed by PascalCase.

This covers the common cases. Constants follow PascalCase too (more on that in a moment), and there are a handful of suffix conventions for special kinds of types.

Prefix and Suffix Conventions

Some kinds of types and members carry a prefix or suffix that signals their role. These aren't optional flourishes, they're part of the .NET design guidelines and the BCL follows them everywhere.

`I` prefix on interfaces. Every interface in the .NET base class library starts with I: IDisposable, IEnumerable, IComparable. The I makes it obvious at a glance whether a type is an interface or a class. Without it, you'd have to look up the declaration to find out.

`T` prefix on generic type parameters. A single T is fine when the type has just one generic parameter. When there are several, use a descriptive name with T in front: TKey, TValue, TItem, TResult.

`Async` suffix on async methods. A method that returns Task or Task<T> and is meant to be awaited gets an Async suffix. This is a convention from the Task-based Asynchronous Pattern (TAP), and the .NET runtime and ASP.NET Core both follow it strictly.

`Exception` suffix on custom exception types. Any class that derives from Exception should end in Exception. The compiler doesn't enforce this, but every exception in the BCL follows it, and analyzers will warn when you break the pattern.

`Attribute` suffix on custom attribute classes. Attribute types end in Attribute. When you apply them, the compiler lets you drop the suffix, so [Route] and [RouteAttribute] are both valid spellings of the same attribute.

`EventArgs` suffix on custom event argument classes. Following EventArgs (the base class) keeps the pattern obvious.

Naming by Member Kind

Here is the complete reference, with one example per member kind, all in the e-commerce domain.

Member KindCasingExample
ClassPascalCaseCustomerOrder
StructPascalCaseMoney
RecordPascalCaseOrderConfirmation
InterfaceI + PascalCaseIOrderRepository
EnumPascalCaseOrderStatus
Enum memberPascalCaseOrderStatus.Shipped
NamespacePascalCaseShopApp.Orders
MethodPascalCaseCalculateTotal
Async methodPascalCase + AsyncPlaceOrderAsync
PropertyPascalCaseTotalAmount
EventPascalCaseOrderPlaced
Public fieldPascalCaseMaxItemsPerOrder (rare; prefer property)
Protected fieldPascalCaseCache (rare; prefer property)
Private field_camelCase_items
Private static field_camelCase or s_camelCase_logger or s_cache
Constant (any access)PascalCaseDefaultPageSize
Local variablecamelCasesubtotal
ParametercamelCaseproductId
Generic type parameterT or T + PascalCaseT, TKey, TValue
Custom exceptionPascalCase + ExceptionOrderNotFoundException
Custom attributePascalCase + AttributeRouteAttribute
Custom event argsPascalCase + EventArgsOrderPlacedEventArgs

One row worth a second look: constants. In older C and Java code, constants are SCREAMING_SNAKE_CASE (MAX_RETRIES). Modern C# uses PascalCase even for const fields. The .NET base class library has been consistent on this for years (look at Math.PI, int.MaxValue, string.Empty).

If you see all-caps constants in C# code, it's usually because the author was carrying habits over from another language. New code should use PascalCase.

What to Avoid

A few patterns come up in older code or from developers moving from other languages. They're not illegal, but they make code harder to read.

Hungarian notation. Embedding the type in the name (strCustomerName, iOrderCount, bIsActive) was popular in 1990s Windows code. C# is statically typed and IDEs show the type on hover, so the prefix adds noise without information. Use customerName, orderCount, isActive instead.

All-caps constants. As covered above, C# uses PascalCase for constants. MAX_RETRIES looks like C or Java code, not C#.

Abbreviations. usrNm, qty, addr, pwd, msg save a few keystrokes and cost a lot of clarity. Write userName, quantity, address, password, message. Common abbreviations like Id, Url, Db are fine since they're widely understood, but treat them as words (OrderId, not OrderID, in modern C# style).

Single-letter names. n, x, s give the reader no help. The two acceptable cases are loop counters in short loops (for (int i = 0; i < items.Count; i++)) and single-parameter generics (List<T>). Outside those, use a real name.

Negated booleans. isNotReady, hasNoItems, cannotShip force the reader to mentally flip the value when reading conditions. if (!isNotReady) is a double negative. Use the positive form (isReady, hasItems, canShip) and let the caller add ! if they need the negation.

Here's a side-by-side comparison:

Bad NameBetter NameWhy
strCustomerNamecustomerNameHungarian prefix adds no information in a typed language
MAX_RETRIESMaxRetriesAll-caps is a C/Java convention; C# uses PascalCase for constants
usrNmuserNameVowel-removed abbreviations hurt readability
nitemCountSingle letter gives no clue about meaning
isNotReadyisReadyNegated booleans read awkwardly in conditions
dataorderItemsGeneric word; doesn't say what kind of data
tempformattedAddress"Temp" tells you it's temporary, not what it holds
MgrManagerTruncation is rarely worth the saved letters
ProductIDProductIdModern .NET treats Id as a word, not an acronym
GetOrder (async)GetOrderAsyncMissing Async suffix on a Task-returning method

Naming for Clarity

Casing tells you how to capitalize. Word choice tells you what to call the thing in the first place. A few rules from the .NET design guidelines that pay off:

Classes and properties get nouns. A class represents a thing, so its name should be a noun: Order, Customer, ShoppingCart. A property is data the object has, also a noun: TotalAmount, ShippingAddress, OrderDate. Avoid verb-shaped class names like OrderProcessor unless the class genuinely represents an actor that processes orders.

Methods get verbs. A method does something, so it should be a verb: Calculate, Send, Validate. Pair it with the object of the action: CalculateTotal, SendInvoice, ValidateAddress. Methods that return a value can use Get: GetCustomerById. Methods that return a boolean often start with Is, Has, Can, or Should: IsValid, HasShipped.

Booleans start with `Is`, `Has`, `Can`, or `Should`. This applies to both fields and properties. The prefix turns the name into a yes/no question, which reads naturally in conditions:

Collections are plural. A field holding many things should be named in the plural: products, orders, items, _lineItems. A single item is singular: product, order, item. The contrast tells the reader at a glance whether they're dealing with one or many.

Name the abstraction, not the implementation. Call the field _items, not _itemList. If you change List<OrderItem> to HashSet<OrderItem> later, the name still fits. The type system already tells you it's a list, so the name doesn't need to.

Avoid stuttering. A property named Order.OrderDate repeats "Order" in both places. Inside the Order class, Date is enough. Stuttering is common in code generated from database schemas, but it's worth cleaning up.

Tools That Enforce Naming

You don't have to police these conventions by hand. The .NET ecosystem ships with tools that catch violations as you type.

Roslyn analyzers. The C# compiler is built on Roslyn, and Roslyn ships with built-in analyzers that flag naming issues. Rules like CA1707 ("Identifiers should not contain underscores" for public members) and CA1715 ("Interfaces should start with I") run during build. You'll see them as warnings in your IDE.

EditorConfig. The .editorconfig file lets you define naming rules per project. Most teams check it into source control so everyone gets the same rules without configuring their IDE. Here's a minimal example that enforces _camelCase for private fields:

The IDE reads this file and underlines any private field that doesn't start with _. The fix is one keystroke away.

StyleCop. StyleCop is a popular third-party analyzer that adds stricter style rules on top of the built-in ones. It enforces things like file headers, ordering of members within a class, and consistent naming. Many teams run StyleCop in CI so non-conforming code fails the build.

The practical workflow is simple. Pick a style (PascalCase for public, camelCase for local, _camelCase for private), put it in .editorconfig, and let the tooling handle the rest. New team members get the conventions automatically the first time they open the project.

Summary

  • Identifiers must start with a letter or underscore, may contain letters/digits/underscores, are case-sensitive, and can't be reserved keywords. The @ prefix escapes a keyword but should be avoided in new code.
  • PascalCase is used for types, methods, public/protected members, properties, events, enums, namespaces, and constants. camelCase is used for local variables and parameters. _camelCase is the convention for private fields.
  • Interfaces start with I (IOrderRepository), generic type parameters with T (T, TKey, TValue), async methods end in Async, exceptions in Exception, attributes in Attribute, and event args in EventArgs.
  • Modern C# uses PascalCase for constants, not the all-caps style from C or Java. The .NET base class library has been consistent on this for years (Math.PI, int.MaxValue).
  • Avoid Hungarian notation, vowel-dropped abbreviations, single-letter names outside short loops and generics, and negated booleans. Use nouns for classes and properties, verbs for methods, Is/Has/Can/Should prefixes for booleans, and plural names for collections.
  • Roslyn analyzers, EditorConfig files (dotnet_naming_rule.*), and StyleCop enforce these conventions automatically. Put the rules in .editorconfig once and the IDE flags violations for everyone on the team.

That wraps up the Basic Syntax section. You've now covered the building blocks of C#: variables, data types, operators, expressions, statements, comments, formatting, and naming. The Control Flow section is next, where these pieces drive decisions and repeat work, covering if-else, switch statements and switch expressions, and the various loop forms (for, while, do-while, foreach) that drive most everyday C# code.