C# Classes — Class Syntax, Fields, Properties, Auto-Properties, and Init Setters
In this tutorial, you will learn about C# Classes. We cover key concepts, practical examples, and best practices to help you master this topic.
C# classes are reference types that encapsulate data (fields and properties) and behavior (methods) into reusable blueprints for creating objects in object-oriented programming.
What You'll Learn
You will master C# class design: defining classes with fields and properties, using auto-properties for concise getter/setter patterns, leveraging init-only setters for immutable object creation, expression-bodied members for compact syntax, and property validation with full property syntax.
Why It Matters
Classes are the foundation of object-oriented programming in .NET. Understanding property patterns — especially the distinction between fields, auto-properties, and init-only setters — is critical for designing clean APIs. Init-only setters, introduced in C# 9, enable immutable data models that work with object initializers, a pattern widely used in modern C# codebases.
Real-World Use
ASP.NET Core models use classes with properties for request/response DTOs. Entity Framework Core entities are classes with properties mapping to database columns. Configuration options classes use init-only setters for immutable settings. Complex business logic is organized into classes following SOLID principles.
Learning Path
graph LR
A["08: Loops"] --> B["09: Classes"]
B --> C["10: Constructors"]
C --> D["11: Encapsulation"]
D --> E["12: Inheritance"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
style E fill:#4a90d9,stroke:#2c5f8a,color:#fff
Basic Class Structure
public class Person
{
// Fields (private, backing storage)
private string _name;
private int _age;
// Constructor
public Person(string name, int age)
{
_name = name;
_age = age;
}
// Methods
public void Introduce()
{
Console.WriteLine($"Hi, I'm {_name} and I'm {_age} years old.");
}
}
// Usage
var person = new Person("Alice", 30);
person.Introduce();
Expected output:
Hi, I'm Alice and I'm 30 years old.
Fields
Fields store data directly in a class. They are typically private to maintain Encapsulation:
public class BankAccount
{
// Instance fields
private string _accountNumber;
private decimal _balance;
private List<string> _transactions = new();
// Static field (shared across all instances)
private static int _nextAccountNumber = 1000;
// Readonly field (set once in constructor)
private readonly DateTime _createdAt;
// Constant field (implicitly static)
public const decimal MinimumBalance = 10m;
public BankAccount(string accountNumber)
{
_accountNumber = accountNumber;
_createdAt = DateTime.UtcNow;
}
}
Properties
Properties expose fields with controlled access. They are the primary way to expose data in C#:
Auto-Properties (C# 3+)
The simplest form:
public class Product
{
// Auto-properties: compiler generates backing field
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// Read-only auto-property (no setter)
public DateTime CreatedAt { get; } = DateTime.UtcNow;
// Auto-property with private setter
public int StockCount { get; private set; }
}
Full Property with Validation
public class Temperature
{
private double _celsius;
public double Celsius
{
get => _celsius;
set
{
if (value < -273.15)
throw new ArgumentException("Temperature cannot be below absolute zero");
_celsius = value;
}
}
// Computed property (no backing field)
public double Fahrenheit => _celsius * 9 / 5 + 32;
// Expression-bodied property (C# 6+)
public string Description => Celsius switch
{
< 0 => "Freezing",
< 20 => "Cold",
< 30 => "Warm",
_ => "Hot"
};
}
Init-Only Setters (C# 9+)
Init-only setters enable immutable properties that can be set during object initialization but not afterward:
public class UserProfile
{
public int Id { get; init; }
public string Username { get; init; }
public string Email { get; init; }
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
}
// Usage with object initializer (immutable after creation)
var profile = new UserProfile
{
Id = 1,
Username = "alice",
Email = "alice@example.com"
};
// This would cause a compilation error:
// profile.Email = "new@example.com"; // Error: init-only property
Init-only properties are essential for immutable data models and work perfectly with record types.
Required Properties (C# 11+)
required modifier enforces that callers must initialize the property:
public class Config
{
public required string ServerUrl { get; init; }
public required int Port { get; init; }
public int TimeoutSeconds { get; init; } = 30;
}
// Compilation error if ServerUrl or Port are omitted:
// var config = new Config(); // Error!
var config = new Config
{
ServerUrl = "https://api.example.com",
Port = 443
};
Expression-Bodied Members
Simplify methods and properties that consist of a single expression:
public class Circle
{
public double Radius { get; }
public Circle(double radius)
{
Radius = radius;
}
// Expression-bodied method
public double Area() => Math.PI * Radius * Radius;
// Expression-bodied property
public double Circumference => 2 * Math.PI * Radius;
// Expression-bodied static method
public static Circle FromDiameter(double diameter) => new(diameter / 2);
}
Static Members
Static members belong to the type itself, not to any instance:
public class Counter
{
// Static field
private static int _totalCount;
// Static property
public static int TotalCount => _totalCount;
// Instance method
public int InstanceId { get; }
public Counter()
{
_totalCount++;
InstanceId = _totalCount;
}
// Static method
public static void Reset() => _totalCount = 0;
}
var c1 = new Counter();
var c2 = new Counter();
Console.WriteLine($"Instance IDs: {c1.InstanceId}, {c2.InstanceId}");
Console.WriteLine($"Total: {Counter.TotalCount}"); // 2
Nested Classes
public class Library
{
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
private List<Book> _books = new();
public void AddBook(string title, string author)
{
_books.Add(new Book { Title = title, Author = author });
}
}
Partial Classes
Split a class definition across multiple files (used extensively with source generators):
// File1.cs
public partial class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
// File2.cs
public partial class Employee
{
public string FullName => $"{FirstName} {LastName}";
}
Common Mistakes
Mistake 1: Public Fields Instead of Properties
Always use properties, not public fields. Properties support validation, data binding, versioning, and can be defined in interfaces. Fields are implementation details.
Mistake 2: Throwing Exceptions From Auto-Property Getters
Getters should not throw exceptions or have side effects. Use a method if computation is expensive or can fail.
Mistake 3: Forgetting to Initialize Collection Properties
// Dangerous: _items could be null
public List<string> Items { get; set; }
// Safe: initialize inline
public List<string> Items { get; set; } = new();
Mistake 4: Using Mutable Properties for Value Objects
Value objects should be immutable. Use init setters or record types instead of mutable get; set; properties.
Mistake 5: Exposing Internal State via Reference Properties
Returning a List<T> property gives external code a reference to your internal list. Use IReadOnlyList<T> or return a copy.
Mistake 6: Overusing Static Classes
Static classes cannot implement interfaces, be passed as parameters, or be mocked for testing. Prefer instance classes with Dependency Injection.
Practice Questions
- What is the difference between a field and a property in C#?
- How does an init-only setter differ from a regular setter?
- Why are public fields considered bad practice in C#?
- Write a class
Rectanglewith auto-properties for Width and Height, a computed property for Area, and validation that prevents negative values. - What is the
requiredmodifier and when would you use it?
Challenge
Design an immutable Order class with init-only properties (OrderId, CustomerName, Items, OrderDate). Include a computed property for Total. Demonstrate that properties cannot be modified after initialization.
FAQ
Mini Project
Create a simple inventory management system:
public class InventoryItem
{
public required string Sku { get; init; }
public required string Name { get; set; }
public string Category { get; set; } = "Uncategorized";
public int Stock { get; private set; }
public decimal Price { get; set; }
public DateTime AddedDate { get; init; } = DateTime.UtcNow;
public decimal TotalValue => Stock * Price;
public void AddStock(int quantity)
{
if (quantity <= 0)
throw new ArgumentException("Quantity must be positive");
Stock += quantity;
}
public bool RemoveStock(int quantity)
{
if (quantity <= 0)
throw new ArgumentException("Quantity must be positive");
if (quantity > Stock) return false;
Stock -= quantity;
return true;
}
}
public class Inventory
{
private List<InventoryItem> _items = new();
public void AddItem(InventoryItem item) => _items.Add(item);
public int TotalItems => _items.Sum(i => i.Stock);
public decimal TotalValue => _items.Sum(i => i.TotalValue);
public void Display()
{
foreach (var item in _items)
{
Console.WriteLine(
$"{item.Sku}: {item.Name} | Stock: {item.Stock} | " +
$"Price: {item.Price:C} | Value: {item.TotalValue:C}");
}
Console.WriteLine($"\nTotal items: {TotalItems}");
Console.WriteLine($"Total value: {TotalValue:C}");
}
}
var inv = new Inventory();
inv.AddItem(new InventoryItem { Sku = "LAP001", Name = "Laptop", Price = 999.99m });
inv.AddItem(new InventoryItem { Sku = "MOU002", Name = "Mouse", Price = 24.99m });
inv.InventoryItems[0].AddStock(5);
inv.InventoryItems[1].AddStock(20);
inv.InventoryItems[1].RemoveStock(3);
inv.Display();
Expected output:
LAP001: Laptop | Stock: 5 | Price: $999.99 | Value: $4,999.95
MOU002: Mouse | Stock: 17 | Price: $24.99 | Value: $424.83
Total items: 22
Total value: $5,424.78
What's Next
You have mastered class design in C# including properties, init-only setters, and expression-bodied members. The next lesson covers constructors: default, parameterized, static, and primary constructors.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro