Learn how to define classes in C#, what they can contain, and how the different class kinds (nested, static, partial, sealed, abstract) change their behavior.
Level: 🟢 Beginner Prerequisites: 01. Introduction to OOP
- What is a Class?
- Class Syntax
- Class Members
- Class Design
- Nested Classes
- Static Classes
- Partial Classes
- Sealed Classes
- Abstract Classes
- Real-World Example
- Interview Questions
By the end of this chapter you will be able to:
- Define a class and explain what it represents.
- Write a class with fields, properties, constructors and methods.
- List the kinds of members a class can contain.
- Apply basic class design rules (single responsibility, naming, encapsulation).
- Use nested, static, partial, sealed and abstract classes and know when each is appropriate.
A class is a reference type that defines the structure and behavior of objects. It describes:
- What data an object holds (state).
- What operations an object can perform (behavior).
- How an object is created and initialized (constructors).
A class by itself does nothing at runtime. You create objects (instances) from it using new (see 03. Objects).
[access-modifier] [class-modifier] class ClassName [: BaseClass, Interface1, ...]
{
// members
}
public class Person
{
// members go here
}Common class modifiers
| Modifier | Meaning |
|---|---|
public |
Accessible from any assembly |
internal |
Accessible only within the same assembly (default for top-level types) |
abstract |
Cannot be instantiated; meant to be a base class |
sealed |
Cannot be inherited |
static |
Cannot be instantiated; contains only static members |
partial |
Class definition is split across multiple parts |
| Member | Purpose | Chapter |
|---|---|---|
| Fields | Variables that store state | 04 |
| Properties | Controlled access to state | 05 |
| Methods | Behavior | 06 |
| Constructors | Initialize new objects | 07 |
| Constants | Compile-time fixed values (const) |
04 |
| Indexers | Array-like access via obj[i] |
17 |
| Events | Notifications raised by the object | n/a |
| Operators | Custom operator behavior (e.g., +) |
n/a |
| Finalizers | Cleanup before garbage collection | 23 |
| Nested types | Types declared inside a class | This chapter |
📝 Members declared without an access modifier are
privateby default. Top-level types without a modifier areinternal.
Classes are the primary building block of C# programs. They let you:
- Group related data and behavior into one named unit.
- Create many objects from one definition.
- Hide implementation details behind a public surface.
- Build larger designs through inheritance, interfaces and composition.
Poorly designed classes (too large, too open, unclear purpose) are the most common cause of hard-to-maintain code.
namespace Shop; // file-scoped namespace (C# 10+)
public class Product // class declaration
{
private decimal _price; // field
public string Name { get; } // property
public Product(string name, decimal price) // constructor
{
Name = name;
_price = price;
}
public decimal GetPriceWithTax(decimal rate) // method
=> _price * (1 + rate);
}Special class forms
public abstract class Shape { } // abstract
public sealed class Circle : Shape { } // sealed
public static class MathHelper { } // static
public partial class Customer { } // partial
public class Outer
{
public class Inner { } // nested
}public class Student
{
// Fields
private int _score;
// Properties
public string Name { get; }
public int Score => _score;
// Constructor
public Student(string name)
{
Name = name;
}
// Methods
public void AddScore(int points)
{
if (points < 0)
throw new ArgumentOutOfRangeException(nameof(points));
_score += points;
}
public void PrintReport()
{
Console.WriteLine($"{Name}: {_score} points");
}
}
var alice = new Student("Alice");
alice.AddScore(40);
alice.AddScore(25);
alice.PrintReport();
var bob = new Student("Bob");
bob.AddScore(70);
bob.PrintReport();Output
Alice: 65 points
Bob: 70 points
One class, two independent objects, each with its own _score.
A Playlist class that uses a nested class to hide an implementation detail, and a static class for a stateless utility.
public class Playlist
{
// Nested class: an implementation detail nobody outside needs
private class Track
{
public string Title { get; }
public TimeSpan Duration { get; }
public Track(string title, TimeSpan duration)
{
Title = title;
Duration = duration;
}
}
private readonly List<Track> _tracks = new();
public string Name { get; }
public Playlist(string name) => Name = name;
public void AddTrack(string title, int seconds)
=> _tracks.Add(new Track(title, TimeSpan.FromSeconds(seconds)));
public TimeSpan TotalDuration
{
get
{
var total = TimeSpan.Zero;
foreach (var track in _tracks)
total += track.Duration;
return total;
}
}
}
// Static class: stateless helper, no instances needed
public static class DurationFormatter
{
public static string Format(TimeSpan duration)
=> $"{(int)duration.TotalMinutes}:{duration.Seconds:D2}";
}
var playlist = new Playlist("Focus");
playlist.AddTrack("Intro", 125);
playlist.AddTrack("Deep Work", 310);
Console.WriteLine($"{playlist.Name}: {DurationFormatter.Format(playlist.TotalDuration)}");Output
Focus: 7:15
Callers never see Track. Its design can change freely without affecting any other code.
| Guideline | Why |
|---|---|
| One clear responsibility per class | Easier to understand, test and change |
Singular noun, PascalCase names (Invoice, not invoices) |
.NET naming conventions |
| Private state, public behavior | Protects invariants (see 08. Encapsulation) |
| One top-level class per file, file named after the class | Easier navigation |
| Prefer small, cohesive classes | Avoids "god classes" |
A class declared inside another class.
public class Order
{
public class Line
{
public string Product { get; init; } = "";
public int Quantity { get; init; }
}
private readonly List<Line> _lines = new();
public void Add(string product, int qty)
=> _lines.Add(new Line { Product = product, Quantity = qty });
}
var line = new Order.Line { Product = "Pen", Quantity = 3 }; // accessed via outer name- Nested types are
privateby default. - A nested type can access the private members of its containing type (through an instance reference).
- Use them for helpers that belong to exactly one outer class.
public static class TemperatureConverter
{
public static double CelsiusToFahrenheit(double c) => c * 9 / 5 + 32;
}
double f = TemperatureConverter.CelsiusToFahrenheit(100); // 212- Cannot be instantiated (
newis not allowed) or inherited. - Can contain only static members.
- Good for stateless utilities and extension methods (see 13. Static Members).
One class split across multiple files in the same assembly and namespace.
// Customer.cs
public partial class Customer
{
public string Name { get; set; } = "";
}
// Customer.Validation.cs
public partial class Customer
{
public bool IsValid() => !string.IsNullOrWhiteSpace(Name);
}- Every part must use the
partialkeyword. - The compiler merges the parts into one class.
- Common uses: code generators (designer files, source generators) and separating generated code from hand-written code.
- Do not use it just to hide a class that is too large; split the responsibilities instead.
public sealed class Circle : Shape { }
// public class SpecialCircle : Circle { } // ❌ compile error: cannot derive from sealed type- Prevents inheritance.
- Useful when a class is not designed to be extended, for security or design clarity.
- Details in 14. Sealed Members.
public abstract class Shape
{
public string Name { get; }
protected Shape(string name) => Name = name;
public abstract double Area(); // no body: derived classes must implement
public void Describe() => Console.WriteLine($"{Name}: area {Area():F2}");
}
public sealed class Circle : Shape
{
private readonly double _radius;
public Circle(double radius) : base("Circle") => _radius = radius;
public override double Area() => Math.PI * _radius * _radius;
}
Shape s = new Circle(2);
s.Describe(); // Circle: area 12.57
// var x = new Shape("x"); // ❌ cannot create an instance of an abstract class- Cannot be instantiated directly.
- May contain abstract members (no body) and normal members (with body).
- Covered fully in 11. Abstraction and 15. Abstract Class vs Interface.
classDiagram
class Student {
-int _score
+string Name
+int Score
+Student(string name)
+AddScore(int points)
+PrintReport()
}
classDiagram
class Shape {
<<abstract>>
+string Name
+Area()* double
+Describe()
}
class Circle {
<<sealed>>
-double _radius
+Area() double
}
Shape <|-- Circle
flowchart TD
C[Class Kinds]
C --> N[Normal]
C --> A[Abstract: cannot instantiate]
C --> S[Sealed: cannot inherit]
C --> ST[Static: no instances, static members only]
C --> P[Partial: split across files]
C --> NE[Nested: declared inside another class]
(Larger diagrams live in /diagrams.)
| Kind | Instantiable? | Inheritable? | Can contain | Typical use |
|---|---|---|---|---|
| Normal | ✅ | ✅ | Any members | General modeling |
| Abstract | ❌ | ✅ (meant to be) | Abstract + concrete members | Shared base with required overrides |
| Sealed | ✅ | ❌ | Any members | Final implementations |
| Static | ❌ | ❌ | Static members only | Utilities, extension methods |
| Partial | Depends on other modifiers | Depends | Any members | Generated + hand-written code |
| Nested | Depends on accessibility | Depends | Any members | Private helpers of one outer class |
| Aspect | class |
struct |
|---|---|---|
| Type category | Reference type | Value type |
| Assignment | Copies the reference | Copies the value |
| Inheritance | Supports class inheritance | No inheritance from other structs/classes |
| Default value | null |
All fields zeroed |
| Best for | Entities with identity and behavior | Small, immutable value-like data |
Where a value lives (stack, heap, register) is a runtime detail. Rely on semantics (copy vs reference), not on memory location. See 20. Boxing & Unboxing.
- ❌ Making every member
public. This breaks encapsulation. Fix: default toprivate, expose only what callers need. - ❌ God classes. One class that handles validation, storage and printing. Fix: split by responsibility.
- ❌ Static class holding mutable state. It behaves like a hidden global variable and makes testing hard. Fix: use instances, or keep static classes stateless.
- ❌ Using inheritance for reuse alone. If it is not an is-a relationship, use composition.
- ❌ Forgetting
partialon one part. All parts of a partial class must carry the keyword, otherwise you get a duplicate-definition error. - ❌ Trying to instantiate an abstract or static class. Instantiate a concrete derived class (abstract) or call static members directly (static).
- ❌ Assuming a nested class is automatically accessible. Nested types are
privateunless declared otherwise.
- Keep classes small and cohesive.
- Use nouns for class names and verbs for method names.
- Start with
privateand widen accessibility only when needed. - Mark classes
sealedby default unless you intend them to be inherited. - Keep static classes stateless.
- Use nested classes only for helpers tied to one outer class.
- Use partial primarily for generated code, not as an organizing shortcut.
- Initialize objects to a valid state through constructors.
Q1. What is a class in C#?
A class is a reference type that defines the data (fields, properties) and behavior (methods) that its objects will have. It is a blueprint from which instances are created with new.
Q2. What kinds of members can a class contain?
Fields, constants, properties, methods, constructors, finalizers, indexers, events, operators and nested types.
Q3. What is the default access modifier for a class and for its members?
A top-level class defaults to internal. Members of a class (including nested types) default to private.
Q4. What is a static class and when would you use it?
A static class cannot be instantiated or inherited and may contain only static members. It is used for stateless utility functions and for hosting extension methods.
Q5. What is a partial class?
A class whose definition is split across multiple files using the partial keyword. The compiler merges all parts into a single class. It is mostly used to separate generated code from hand-written code.
Q6. What is the difference between an abstract class and a sealed class?
An abstract class cannot be instantiated and is designed to be inherited, often with members that derived classes must implement. A sealed class can be instantiated but cannot be inherited.
Q7. Can a class be both `abstract` and `sealed`?
No. They are contradictory: an abstract class must be inherited to be useful, and a sealed class cannot be inherited. A static class is implicitly both abstract and sealed in the compiled form, but you cannot write both modifiers yourself.
Q8. Why use a nested class?
To define a helper type that logically belongs to one outer class, keeping it out of the public API. A nested type can also access the outer type's private members through an instance.
Q9. What is the difference between a class and a struct?
A class is a reference type: variables hold references and assignment copies the reference. A struct is a value type: assignment copies the value. Classes support inheritance; structs do not inherit from other types (they can implement interfaces).
| # | Problem | Difficulty | Solution |
|---|---|---|---|
| 1 | Create a Book class with Title, Author and a Describe() method. |
🟢 | View → |
| 2 | Create a Rectangle class with width, height, Area() and Perimeter(). |
🟢 | View → |
| 3 | Write a static class StringHelper with a method that reverses a string. |
🟢 | View → |
| 4 | Create an abstract Employee class with an abstract CalculatePay(), then implement FullTimeEmployee and PartTimeEmployee. |
🟡 | View → |
| 5 | Split a Customer class into two partial files: data in one, validation in the other. |
🟡 | View → |
| 6 | Build an Order class with a nested Line class and a method computing the order total. |
🟡 | View → |
| 7 | Mark a class sealed, then try to inherit from it and explain the compiler error. |
🟢 | View → |
Starter files: /exercises/02-classes
- A class is a reference-type blueprint defining state and behavior.
- Class members include fields, properties, methods, constructors, indexers, events, operators, finalizers and nested types.
- Top-level types default to
internal; members default toprivate. - Nested classes hide helpers inside one outer class.
- Static classes are stateless utility holders: no instances, no inheritance.
- Partial classes split one class across files, mainly for generated code.
- Sealed classes cannot be inherited; abstract classes cannot be instantiated.
- Good class design means small, cohesive, well-encapsulated types.
⬅ Previous: Introduction to OOP • 🏠 Main README • Next: Objects ➡