Classes and objects form the foundation of C# and .NET development. Whether you’re building web applications, desktop software, or APIs, you’ll use these concepts every day. Understanding them properly will make you a more effective and confident developer.
🎯 What You’ll Learn
This guide covers the fundamental building blocks of C# programming:
- Classes – Blueprints for creating objects
- Fields – Data storage within classes
- Properties – Controlled access to data
- Methods – Actions and behaviors
- Constructors – Object initialization
- Class Types – Static, Sealed, Abstract, Partial, and Nested classes
1. Introduction to Classes
What is a Class?
A class is a blueprint that defines the structure and behavior of objects. Think of it as a template – like a house plan that defines rooms and features, but each house built from it is unique.
Example:
/// <summary>
/// A basic class demonstrating fundamental concepts
/// Class = Blueprint for creating objects
/// Object = Instance of a class
/// </summary>
public class Student
{
// This is a class - it defines what a student should look like
// But it's not an actual student until we create an object from it
}
// Creating objects from the class
class Program
{
static void Main()
{
// student1 and student2 are OBJECTS created from the Student CLASS
Student student1 = new Student(); // Object 1
Student student2 = new Student(); // Object 2
// Same class, different objects - like two houses from same blueprint
}
}
2. Fields
What are Fields?
Fields are variables that store data inside a class. They represent the “memory” or “state” of your objects.
Key Points:
- Usually declared as
private
for data protection - Use underscore naming convention:
_fieldName
- Can be
readonly
(set once) orconst
(never changes) static
fields are shared by all objects
Example:
public class Student
{
private string _name; // Instance field
private static int _totalStudents; // Static field (shared)
private readonly int _studentId; // Readonly field
public const int MAX_COURSES = 10; // Constant field
}
3. Properties
What are Properties?
Properties provide controlled access to fields. They act like “smart fields” with built-in validation and security.
Types of Properties:
- Full Property – Custom get/set with validation
- Auto Property –
{ get; set; }
– Compiler creates field automatically - Read-only –
{ get; }
– Cannot be changed after creation - Computed –
=> expression
– Calculated each time
Examples:
public class Person
{
private int _age;
// Full property with validation
public int Age
{
get => _age;
set => _age = value >= 0 ? value : throw new ArgumentException("Age cannot be negative");
}
// Auto property
public string Name { get; set; }
// Read-only property
public DateTime BirthDate { get; }
// Computed property
public bool IsAdult => Age >= 18;
}
4. Methods
What are Methods?
Methods define what a class can DO. They represent the behavior and actions of your objects.
Types of Methods:
- Instance Methods – Called on objects:
person.Walk()
- Static Methods – Called on class:
Math.Max(5, 3)
- Overloaded Methods – Same name, different parameters
Parameter Types:
- Normal –
void Method(int value)
– Pass by value - ref –
void Method(ref int value)
– Can modify original - out –
bool TryParse(string input, out int result)
– Return multiple values - params –
int Sum(params int[] numbers)
– Variable arguments
Example:
public class Calculator
{
// Basic method
public int Add(int a, int b) => a + b;
// Method overloading
public double Add(double a, double b) => a + b;
// Method with out parameter
public bool TryDivide(int a, int b, out double result)
{
if (b == 0) { result = 0; return false; }
result = (double)a / b;
return true;
}
// Static method
public static double ToRadians(double degrees) => degrees * Math.PI / 180;
}
5. Constructors
What are Constructors?
Constructors are special methods that run when creating new objects. They initialize the object with starting values.
Types of Constructors:
- Default – No parameters:
public Car() { }
- Parameterized – With values:
public Car(string brand) { }
- Constructor Chaining – One calls another:
: this(defaultValue)
- Static – Runs once when class is first used
Example:
public class Car
{
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
// Default constructor
public Car() : this("Unknown", "Unknown", DateTime.Now.Year) { }
// Parameterized constructor
public Car(string brand, string model) : this(brand, model, DateTime.Now.Year) { }
// Main constructor
public Car(string brand, string model, int year)
{
Brand = brand ?? throw new ArgumentNullException(nameof(brand));
Model = model ?? throw new ArgumentNullException(nameof(model));
Year = year > 1885 ? year : throw new ArgumentException("Invalid year");
}
}
6. Types of Classes
Static Classes
- Cannot create objects (
new
not allowed) - All members must be static
- Used for utility functions
public static class MathHelper
{
public static double PI = 3.14159;
public static double CircleArea(double radius) => PI * radius * radius;
}
// Usage: MathHelper.CircleArea(5.0)
Sealed Classes
- Cannot be inherited
- Used for security or final implementations
public sealed class ApiKey
{
public string Value { get; }
public ApiKey(string value) => Value = value;
}
// Cannot do: public class ExtendedApiKey : ApiKey { } // ERROR!
Abstract Classes
- Cannot create objects directly
- Must be inherited
- Can have both abstract and regular methods
public abstract class Animal
{
public string Name { get; set; }
public abstract void MakeSound(); // Must implement
public virtual void Sleep() => Console.WriteLine("Sleeping"); // Can override
}
public class Dog : Animal
{
public override void MakeSound() => Console.WriteLine("Woof!");
}
Partial Classes
- Split across multiple files
- Useful for large classes
// File 1: Employee.Core.cs
public partial class Employee
{
public string Name { get; set; }
public decimal Salary { get; set; }
}
// File 2: Employee.Business.cs
public partial class Employee
{
public void GiveRaise(decimal amount) => Salary += amount;
}
Nested Classes
- Classes inside other classes
- Used for helper classes
public class University
{
public string Name { get; set; }
// Nested class - only relevant within University context
public class Student
{
public string Name { get; set; }
public string StudentId { get; set; }
public DateTime EnrollmentDate { get; set; }
}
}
// Usage: var student = new University.Student();
📊 Quick Reference Summary
Class vs Object
public class Car { } // Class = Blueprint
Car myCar = new Car(); // Object = Actual car created from blueprint
Access Modifiers
Modifier | Access Level |
---|---|
private | Only within same class |
public | Anywhere |
protected | Same class + inherited classes |
internal | Same project/assembly |
Property Patterns
public string Name { get; set; } // Read/Write
public string Name { get; private set; } // Public read, private write
public string Name { get; } // Read-only
public string Name { get; init; } // Set only during creation (C# 9)
public string FullName => $"{First} {Last}"; // Computed property
Constructor Patterns
public Person() { } // Default
public Person(string name) { } // Parameterized
public Person(string name) : this(name, 0) { } // Constructor chaining
static Person() { } // Static constructor
Method Patterns
public void DoSomething() { } // No return value
public int Calculate() { return 42; } // Return value
public static int Add(int a, int b) { } // Static method
public virtual void Override() { } // Can be overridden
public override void Override() { } // Overrides parent method
Parameter Patterns
void Method(int value) { } // Normal parameter
void Method(ref int value) { } // Can modify original
bool TryParse(string input, out int result) { } // Return multiple values
int Sum(params int[] numbers) { } // Variable arguments
void Process(in LargeStruct data) { } // Read-only reference
🎯 Best Practices
Naming Conventions
- Classes: PascalCase →
CustomerAccount
,UserService
- Methods: PascalCase →
CalculateTotal()
,SendEmail()
- Properties: PascalCase →
FirstName
,IsActive
- Fields: camelCase with underscore →
_userName
,_totalCount
Design Principles
- Encapsulation – Keep fields private, use properties
- Single Responsibility – Each class should do one thing well
- Validation – Always validate constructor parameters and property setters
- Meaningful Names – Use descriptive names that explain purpose
Common Patterns
// ✅ Good: Proper encapsulation
public class BankAccount
{
private decimal _balance;
public decimal Balance
{
get => _balance;
private set => _balance = value >= 0 ? value : throw new ArgumentException("Balance cannot be negative");
}
public BankAccount(string owner, decimal initialBalance)
{
Owner = owner ?? throw new ArgumentNullException(nameof(owner));
Balance = initialBalance; // Uses property validation
}
}
// ❌ Bad: Public fields, no validation
public class BadBankAccount
{
public decimal Balance; // Anyone can set negative balance!
public string Owner; // No validation
}
📝 Complete Example
Here’s a real-world example that demonstrates all concepts:
/// <summary>
/// A complete example demonstrating all class concepts
/// </summary>
public class ShoppingCart
{
// Fields
private readonly List<CartItem> _items;
private readonly DateTime _createdAt;
private static int _totalCartsCreated = 0;
// Properties
public string CustomerId { get; init; }
public decimal TotalAmount => _items.Sum(item => item.TotalPrice);
public int ItemCount => _items.Count;
public bool IsEmpty => _items.Count == 0;
// Constructor
public ShoppingCart(string customerId)
{
CustomerId = customerId ?? throw new ArgumentNullException(nameof(customerId));
_items = new List<CartItem>();
_createdAt = DateTime.Now;
_totalCartsCreated++;
}
// Methods
public void AddItem(string productName, decimal price, int quantity = 1)
{
if (string.IsNullOrWhiteSpace(productName))
throw new ArgumentException("Product name is required");
if (price < 0)
throw new ArgumentException("Price cannot be negative");
if (quantity <= 0)
throw new ArgumentException("Quantity must be positive");
var existingItem = _items.FirstOrDefault(i => i.ProductName == productName);
if (existingItem != null)
{
existingItem.Quantity += quantity;
}
else
{
_items.Add(new CartItem(productName, price, quantity));
}
}
public bool RemoveItem(string productName)
{
var item = _items.FirstOrDefault(i => i.ProductName == productName);
if (item != null)
{
_items.Remove(item);
return true;
}
return false;
}
public void DisplayCart()
{
Console.WriteLine($"Shopping Cart for Customer: {CustomerId}");
Console.WriteLine($"Created: {_createdAt:yyyy-MM-dd HH:mm}");
Console.WriteLine("Items:");
foreach (var item in _items)
{
Console.WriteLine($" {item}");
}
Console.WriteLine($"Total: ${TotalAmount:F2} ({ItemCount} items)");
}
// Static method
public static string GetStatistics()
{
return $"Total carts created: {_totalCartsCreated}";
}
// Nested class
public class CartItem
{
public string ProductName { get; }
public decimal Price { get; }
public int Quantity { get; set; }
public decimal TotalPrice => Price * Quantity;
public CartItem(string productName, decimal price, int quantity)
{
ProductName = productName;
Price = price;
Quantity = quantity;
}
public override string ToString()
{
return $"{ProductName} - ${Price:F2} x {Quantity} = ${TotalPrice:F2}";
}
}
}
// Usage Example
class Program
{
static void Main()
{
var cart = new ShoppingCart("CUST001");
cart.AddItem("Laptop", 999.99m, 1);
cart.AddItem("Mouse", 29.99m, 2);
cart.AddItem("Laptop", 999.99m, 1); // Adds to existing item
cart.DisplayCart();
Console.WriteLine(ShoppingCart.GetStatistics());
}
}
Output:
Shopping Cart for Customer: CUST001
Created: 2024-12-25 14:30
Items:
Laptop - $999.99 x 2 = $1999.98
Mouse - $29.99 x 2 = $59.98
Total: $2059.96 (2 items)
Total carts created: 1
🎓 Summary
Classes are the foundation of C# programming. They provide:
- Structure – Fields and properties store data
- Behavior – Methods define what objects can do
- Security – Access modifiers control who can access what
- Flexibility – Different class types for different needs
- Reusability – Create multiple objects from one class
Key Takeaways:
- Use properties instead of public fields
- Validate constructor parameters and property setters
- Follow naming conventions and single responsibility principle
- Choose the right class type for your needs
- Leverage modern C# features for cleaner code