Last Updated: May 17, 2026
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.
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:
_).customer and Customer are two different names.int, class, return, if, and so on) as an identifier.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.
_camelCaseC# 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.
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.
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.
Here is the complete reference, with one example per member kind, all in the e-commerce domain.
| Member Kind | Casing | Example |
|---|---|---|
| Class | PascalCase | CustomerOrder |
| Struct | PascalCase | Money |
| Record | PascalCase | OrderConfirmation |
| Interface | I + PascalCase | IOrderRepository |
| Enum | PascalCase | OrderStatus |
| Enum member | PascalCase | OrderStatus.Shipped |
| Namespace | PascalCase | ShopApp.Orders |
| Method | PascalCase | CalculateTotal |
| Async method | PascalCase + Async | PlaceOrderAsync |
| Property | PascalCase | TotalAmount |
| Event | PascalCase | OrderPlaced |
| Public field | PascalCase | MaxItemsPerOrder (rare; prefer property) |
| Protected field | PascalCase | Cache (rare; prefer property) |
| Private field | _camelCase | _items |
| Private static field | _camelCase or s_camelCase | _logger or s_cache |
| Constant (any access) | PascalCase | DefaultPageSize |
| Local variable | camelCase | subtotal |
| Parameter | camelCase | productId |
| Generic type parameter | T or T + PascalCase | T, TKey, TValue |
| Custom exception | PascalCase + Exception | OrderNotFoundException |
| Custom attribute | PascalCase + Attribute | RouteAttribute |
| Custom event args | PascalCase + EventArgs | OrderPlacedEventArgs |
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.
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 Name | Better Name | Why |
|---|---|---|
strCustomerName | customerName | Hungarian prefix adds no information in a typed language |
MAX_RETRIES | MaxRetries | All-caps is a C/Java convention; C# uses PascalCase for constants |
usrNm | userName | Vowel-removed abbreviations hurt readability |
n | itemCount | Single letter gives no clue about meaning |
isNotReady | isReady | Negated booleans read awkwardly in conditions |
data | orderItems | Generic word; doesn't say what kind of data |
temp | formattedAddress | "Temp" tells you it's temporary, not what it holds |
Mgr | Manager | Truncation is rarely worth the saved letters |
ProductID | ProductId | Modern .NET treats Id as a word, not an acronym |
GetOrder (async) | GetOrderAsync | Missing Async suffix on a Task-returning method |
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.
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.
@ prefix escapes a keyword but should be avoided in new code._camelCase is the convention for private fields.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.Math.PI, int.MaxValue).Is/Has/Can/Should prefixes for booleans, and plural names for collections.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.