Back to Blog
C#

C# Nested Class: Usage, Scope, and Design Tradeoffs

c# nested class: Learn how to declare and use C# nested classes, understand their accessibility and lifetime, and decide when nesting is a design improvement.

nested typesC# classestype encapsulationobject-oriented designcode organization
Illustration of a C# nested class showing an outer class containing an inner class with a clear visual boundary.

A C# nested class is a class declared inside another class. The enclosing type is called the outer class. The syntax is straightforward: write the declaration of the inner class within the body of the outer class. The nested class has its own type name that includes the outer class name, such as Outer.Inner. This scoping affects access, instantiation, and how the two types relate at runtime.

Consider a simple example:

public class Order { public int Id { get; set; } public List<OrderItem> Items { get; } = new(); public class OrderItem { public string ProductCode { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } } }

Here OrderItem is a nested class inside Order. Even though OrderItem is public, its accessible name is Order.OrderItem. A consumer of Order would instantiate it like this:

var item = new Order.OrderItem { ProductCode = "ABC", Quantity = 1 };

Nesting is a form of scoping, not inheritance. The nested class does not automatically have access to the outer class's instance state. If you need the nested class to read or write fields of a specific outer object, you must pass a reference explicitly.

How Nested Class Access Works

Nested classes follow the same accessibility rules as other class members, but the enclosing type's scope adds an extra layer. You can apply private, protected, internal, public, or protected internal to a nested class. The chosen modifier controls which code can see the type name itself.

  • public nested class is visible anywhere the outer class is visible.
  • private nested class is only visible inside the outer class body.
  • protected nested class is visible to derived classes of the outer class.
  • internal nested class is visible within the same assembly.

When a nested class is private, it is often used as an implementation detail. For example, a collection class might define a private node type that only the outer class manipulates. This hides the type from the public API, which reduces the surface area that consumers need to understand.

Because the nested type is a member of the outer type, the outer type also has access to all members of the nested type, including private ones. This is a key advantage for helper classes that need to cooperate closely with the outer class.

For instance:

public class Cache<T> { private readonly Dictionary<string, Entry> _entries = new(); public void Add(string key, T value, DateTime expiresAt) { _entries[key] = new Entry(value, expiresAt); } private class Entry { public T Value { get; } public DateTime ExpiresAt { get; } public Entry(T value, DateTime expiresAt) { Value = value; ExpiresAt = expiresAt; } } }

Here Entry is private and is never exposed outside Cache<T>. The outer class can freely access Value and ExpiresAt because private nested types are fully accessible to the enclosing type. This is a common pattern for encapsulating internal state that should not be part of the public contract.

No Automatic Reference to the Outer Instance

A common misconception is that a nested class automatically has access to the outer class's instance members. That is not true. Unlike Java inner classes, C# nested classes do not hold an implicit reference to an outer instance unless you create one yourself. The nested class is essentially a separate type whose name is scoped within the outer type.

So if you write this:

public class Outer { private int _value; public class Inner { public void ShowValue() { Console.WriteLine(_value); // Compiler error: _value is not accessible here } } }

The compiler will report that _value is not accessible in this context because Inner has no enclosing instance. To give Inner access to an Outer instance, you must provide a reference:

public class Outer { private int _value; public class Inner { private readonly Outer _outer; public Inner(Outer outer) { _outer = outer; } public void ShowValue() { Console.WriteLine(_outer._value); } } }

This design gives you explicit control but adds a coupling between the two types. If you do not need that coupling, a nested class works just as well without any reference to the outer instance.

When Nesting Is a Design Improvement

Nesting is most useful when the nested type exists only to serve the outer type and is not meaningful on its own. Typical cases include:

  • Builder types that construct a complex outer object.
  • Result or state types that are only relevant within the outer class.
  • Comparer implementations that are used by a collection class.
  • Node types for linked structures, trees, or caches.

When the nested type is general-purpose, it usually belongs as a top-level class. For example, a Address class that is used across many different entities should not be nested inside Customer. Doing so forces every consumer to write Customer.Address, and it makes reuse awkward.

Here is a practical example of a nested class used as a builder result:

public class Report { public string Title { get; private set; } public IReadOnlyList<Row> Rows { get; private set; } public static Report Build(IEnumerable<DataRecord> records) { // ... implementation ... } public class Row { public string Label { get; set; } public decimal Total { get; set; } } }

Row is tightly related to Report; it is unlikely to appear in another context. Nesting it clarifies that relationship and keeps the codebase cleaner.

Nested Class vs. Separate Top-Level Class

Choosing between a nested class and a separate top-level class is a design decision. Consider the following criteria.

CriterionNested ClassTop-Level Class
Type name lengthLonger (e.g., Outer.Inner)Short (e.g., Inner)
EncapsulationCan be private, hiding it from public APIMust be public or internal to be used outside the namespace
Conceptual couplingTight, clearly signals the relationshipLoose, the relationship is not enforced by syntax
ReusabilityLower, because it is scoped to the outer typeHigher, can be used across many parts of the codebase
External visibilityCan be completely hiddenEven internal classes are visible within the assembly

A nested class is the right choice when the type is an implementation detail of the outer class. A top-level class is better when the type has independent meaning or is likely to be reused elsewhere.

Static Nested Classes and Memory Behavior

A nested class can be declared static, just like a top-level class. This is appropriate when the nested class contains only static members or when you want to group related constants or helper methods. For example:

public class Geometry { public static class Constants { public const double Pi = 3.14159; public const double E = 2.71828; } }

Using Constants as a static nested class gives it a clear namespace and prevents it from being instantiated accidentally.

There is a subtle memory and performance consideration when a nested class is non-static and is used as an instance member of the outer class. If you keep a reference to a nested instance inside the outer instance, then the nested instance lives on the heap as part of the outer object's object graph. That is not a problem in itself. The more important point is that a nested instance does not hold a reference to the outer instance unless you create one. That means a nested instance can be garbage collected independently of the outer instance if no other root references it. If you do store an outer reference inside the nested class, then the two objects form a coupled lifetime: the nested instance will keep the outer instance alive as long as the nested instance is reachable.

In a high-allocating code path, creating many small nested instances can add allocation pressure, but the same is true for small top-level classes. The nesting itself does not introduce a measurable runtime cost; it only affects type naming and accessibility.

Common Mistakes and How to Avoid Them

One common mistake is trying to access outer instance members without a reference, as described earlier. Another is making a nested class public when it is only used internally, which unnecessarily widens the API surface. A third common issue is over-nesting: creating deeply nested types such as Outer.Middle.Inner. While C# allows arbitrary nesting depth, deep nesting makes code hard to read and refactor. Keep nesting to one or two levels at most.

Another mistake is assuming that protected on a nested class gives access to derived classes of the nested class itself. Actually, protected on a nested class means that the nested class is accessible from within the outer class and from types that derive from the outer class. So if you have class Derived : Outer, then Derived can refer to Outer.Inner if Inner is protected. If Inner derives from something else, that does not change its accessibility.

Accessibility and Inheritance Interaction

When a nested class is protected, derived classes of the outer class can access it. That allows a base class to expose a nested helper type to subclasses without making it public. For example:

public abstract class Vehicle { protected class Engine { public void Start() { } } } public class Car : Vehicle { public void Drive() { Engine engine = new Engine(); engine.Start(); } }

This is a neat way to provide shared internal components to subclasses. But it also couples the subclass to that specific type. If you anticipate that subclasses might need a different engine implementation, an interface or an abstract method might be a better design.

Serialization and Reflection Considerations

If you serialize objects that contain nested classes, the type names include the plus sign in fully qualified names. For example, the type name of Order.OrderItem is Order+OrderItem in reflection and in many serialization formats. This can be surprising if you rely on exact type name strings, such as when mapping JSON to .NET types or when using a serializer that stores type information.

Reflection works with nested types, but you need to be aware that typeof(Order.OrderItem) returns a type whose FullName is Order+OrderItem. If you are writing code that parses type names, this can break assumptions. Usually you don't need to care, but for frameworks that generate code based on type names, like some DI containers or serializers, it's worth testing.

Guidelines for Reasonable Use

Use a nested class when all of the following are true:

  • The nested type is only used by the outer class.
  • The nested type has no meaning outside the outer class.
  • The nested type is short and simple.

Do not nest when:

  • The type is reused by several classes.
  • The type is large enough that it deserves its own file.
  • The nesting creates a verbose name that hurts readability.

There is no strict rule about file organization. Many teams keep nested classes in the same file as the outer class, while others prefer separate files with partial classes. If the nested class is private and only a few dozen lines long, keeping it in the same file is usually fine.

A final note: nested classes can be generic, and they can be nested inside generic types. The outer type's type parameters are not implicitly available to the nested type. If the nested class needs the outer type parameters, it must declare its own or you must explicitly qualify them. This is a common source of confusion when moving a top-level generic class into a nested position.

In practice, C# nested classes are a tool for scoping and encapsulation. They are not a way to achieve inheritance or to access the outer instance automatically. When used appropriately, they make the relationship between two types explicit and reduce the public API surface. When overused, they create obscure types and make the code harder to navigate. The decision is, ultimately, a product of how tightly coupled the two types are in your domain.

c# nested class: Practical Usage and Code Examples | RYUSLOG DEV