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) or const (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:

  • Normalvoid Method(int value) – Pass by value
  • refvoid Method(ref int value) – Can modify original
  • outbool TryParse(string input, out int result) – Return multiple values
  • paramsint 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

ModifierAccess Level
privateOnly within same class
publicAnywhere
protectedSame class + inherited classes
internalSame 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

  1. Encapsulation – Keep fields private, use properties
  2. Single Responsibility – Each class should do one thing well
  3. Validation – Always validate constructor parameters and property setters
  4. 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:

  1. Structure – Fields and properties store data
  2. Behavior – Methods define what objects can do
  3. Security – Access modifiers control who can access what
  4. Flexibility – Different class types for different needs
  5. 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

Leave a Reply