Skip to main content

Classes and Objects

Classes are the fundamental building blocks of object-oriented programming in C#. They encapsulate data (fields) and behavior (methods) into a single unit.

Class Declaration and Instantiation​

Basic Class Declaration​

// Basic class declaration
public class Person
{
// Fields (instance variables)
private string _name;
private int _age;

// Properties
public string Name
{
get { return _name; }
set { _name = value; }
}

public int Age
{
get { return _age; }
set { _age = value; }
}

// 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.");
}

public bool IsAdult()
{
return _age >= 18;
}
}

Object Instantiation​

// Creating an object using the constructor
Person person1 = new Person("Alice", 30);

// Using object initializer syntax (C# 3.0+)
Person person2 = new Person("Bob", 25)
{
// Additional property initialization
Name = "Bob Smith" // Updates the Name property after constructor
};

// Using var keyword (type inference)
var person3 = new Person("Charlie", 35);

// Using target-typed new expression (C# 9.0+)
Person person4 = new("David", 40);

// Accessing properties
string name = person1.Name;
int age = person1.Age;

// Setting properties
person1.Name = "Alice Smith";
person1.Age = 31;

// Calling methods
person1.Introduce();
bool isAdult = person1.IsAdult();

Class Members​

Fields​

Fields are variables declared within a class.

public class Counter
{
// Private field (instance variable)
private int _count;

// Public field (generally not recommended, use properties instead)
public int MaxValue;

// Static field (shared across all instances)
public static int TotalCounters;

// Readonly field (can only be assigned in declaration or constructor)
public readonly string Id;

// Const field (compile-time constant)
public const int DefaultMaxValue = 100;

// Static readonly field (runtime constant)
public static readonly DateTime StartupTime = DateTime.Now;

public Counter(string id)
{
Id = id; // Readonly field can be set in constructor
TotalCounters++; // Increment static field
MaxValue = DefaultMaxValue; // Using const value
}
}

// Usage
Counter c1 = new Counter("c1");
Counter c2 = new Counter("c2");

Console.WriteLine(Counter.TotalCounters); // 2 (static field)
Console.WriteLine(Counter.DefaultMaxValue); // 100 (const field)
Console.WriteLine(Counter.StartupTime); // Time when the app started (static readonly)

c1.MaxValue = 200; // Changing public field
// c1.Id = "new-id"; // Error: readonly field cannot be changed after initialization

Properties​

Properties provide a flexible mechanism to read, write, or compute the value of a private field.

public class Temperature
{
// Backing field
private double _celsius;

// Full property with custom getter and setter
public double Celsius
{
get { return _celsius; }
set { _celsius = value; }
}

// Property with validation
public double Fahrenheit
{
get { return _celsius * 9 / 5 + 32; }
set { _celsius = (value - 32) * 5 / 9; }
}

// Auto-implemented property (compiler generates backing field)
public string Scale { get; set; } = "Metric";

// Read-only property (no setter)
public bool IsFreezing
{
get { return _celsius <= 0; }
}

// Expression-bodied property (C# 6+)
public bool IsBoiling => _celsius >= 100;

// Property with private setter
public DateTime LastUpdated { get; private set; }

// Static property
public static double AbsoluteZero { get; } = -273.15;

// Init-only property (C# 9+)
public string Unit { get; init; } = "°C";

public void Update(double celsius)
{
_celsius = celsius;
LastUpdated = DateTime.Now; // Can modify private setter from within the class
}
}

// Usage
Temperature temp = new Temperature();
temp.Celsius = 25;
Console.WriteLine(temp.Fahrenheit); // 77

temp.Scale = "Scientific";
// temp.Unit = "K"; // Error: Init-only property can only be set during initialization

Temperature temp2 = new Temperature { Celsius = 100, Unit = "K" }; // Init-only property set during initialization

Console.WriteLine(Temperature.AbsoluteZero); // -273.15 (static property)

Auto-Implemented Properties​

public class Product
{
// Basic auto-implemented property
public string Name { get; set; }

// Auto-implemented property with default value (C# 6+)
public decimal Price { get; set; } = 0.0m;

// Read-only auto-implemented property
public string SKU { get; }

// Property with private setter
public DateTime CreatedDate { get; private set; }

// Init-only property (C# 9+)
public string Category { get; init; }

// Required property (C# 11+)
public required string Id { get; set; }

public Product(string sku)
{
SKU = sku;
CreatedDate = DateTime.Now;
}
}

// Usage
// Product p1 = new Product("ABC123"); // Error: Required property Id must be set

Product p2 = new Product("ABC123")
{
Id = "P12345",
Name = "Laptop",
Price = 999.99m,
Category = "Electronics"
};

// p2.Category = "Computers"; // Error: Init-only property cannot be changed after initialization
// p2.SKU = "XYZ789"; // Error: Read-only property cannot be changed

Methods​

Methods define the behavior of a class.

public class Calculator
{
// Instance method
public int Add(int a, int b)
{
return a + b;
}

// Method with optional parameters
public double Divide(double a, double b = 1.0)
{
if (b == 0)
throw new DivideByZeroException();
return a / b;
}

// Method with out parameter
public bool TryParse(string input, out int result)
{
return int.TryParse(input, out result);
}

// Method with ref parameter
public void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}

// Static method
public static int Max(int a, int b)
{
return (a > b) ? a : b;
}

// Expression-bodied method (C# 6+)
public int Multiply(int a, int b) => a * b;

// Async method
public async Task<string> FetchDataAsync(string url)
{
using (HttpClient client = new HttpClient())
{
return await client.GetStringAsync(url);
}
}
}

// Usage
Calculator calc = new Calculator();
int sum = calc.Add(5, 3); // 8

double quotient = calc.Divide(10); // 10.0 (uses default value for b)

calc.TryParse("42", out int number); // number = 42

int x = 5, y = 10;
calc.Swap(ref x, ref y); // x = 10, y = 5

int maximum = Calculator.Max(8, 12); // 12 (static method)

Constructors​

Constructors initialize objects of a class.

public class Car
{
// Fields
private string _make;
private string _model;
private int _year;

// Default constructor (no parameters)
public Car()
{
_make = "Unknown";
_model = "Unknown";
_year = DateTime.Now.Year;
Console.WriteLine("Default constructor called");
}

// Parameterized constructor
public Car(string make, string model, int year)
{
_make = make;
_model = model;
_year = year;
Console.WriteLine("Parameterized constructor called");
}

// Constructor with optional parameters
public Car(string make, string model, int year = 2023)
{
_make = make;
_model = model;
_year = year;
}

// Constructor chaining using 'this'
public Car(string make) : this(make, "Unknown", DateTime.Now.Year)
{
Console.WriteLine("Single parameter constructor called");
}

// Static constructor (called once before any instance is created or static members are accessed)
static Car()
{
Console.WriteLine("Static constructor called");
// Initialize static fields/properties
}

// Properties
public string Make { get => _make; set => _make = value; }
public string Model { get => _model; set => _model = value; }
public int Year { get => _year; set => _year = value; }

// Method
public string GetInfo() => $"{_year} {_make} {_model}";
}

// Usage
Car car1 = new Car(); // Default constructor
Car car2 = new Car("Toyota", "Camry", 2022); // Parameterized constructor
Car car3 = new Car("Honda"); // Constructor with one parameter, chains to parameterized constructor

Destructors/Finalizers​

Destructors (also called finalizers) are used to perform cleanup operations before an object is garbage collected.

public class ResourceHolder
{
private IntPtr _handle; // Unmanaged resource
private bool _disposed = false;

public ResourceHolder(IntPtr handle)
{
_handle = handle;
Console.WriteLine("Resource acquired");
}

// Finalizer/Destructor
~ResourceHolder()
{
Dispose(false);
Console.WriteLine("Finalizer called");
}

// Implement IDisposable pattern for deterministic cleanup
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Prevent finalizer from running
}

protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Dispose managed resources
}

// Dispose unmanaged resources
if (_handle != IntPtr.Zero)
{
// CloseHandle(_handle);
_handle = IntPtr.Zero;
Console.WriteLine("Resource released");
}

_disposed = true;
}
}
}

// Usage
using (ResourceHolder resource = new ResourceHolder(new IntPtr(1234)))
{
// Use the resource
} // Dispose is called automatically at the end of the block

Indexers​

Indexers allow objects to be indexed like arrays.

public class StringCollection
{
private List<string> _items = new List<string>();

// Basic indexer
public string this[int index]
{
get
{
if (index < 0 || index >= _items.Count)
throw new IndexOutOfRangeException();
return _items[index];
}
set
{
if (index < 0 || index >= _items.Count)
throw new IndexOutOfRangeException();
_items[index] = value;
}
}

// Overloaded indexer with string parameter
public string this[string name]
{
get
{
return _items.FirstOrDefault(item => item.Equals(name, StringComparison.OrdinalIgnoreCase));
}
}

// Method to add items
public void Add(string item)
{
_items.Add(item);
}

// Property to get count
public int Count => _items.Count;
}

// Usage
StringCollection fruits = new StringCollection();
fruits.Add("Apple");
fruits.Add("Banana");
fruits.Add("Orange");

string fruit = fruits[1]; // "Banana"
fruits[1] = "Mango"; // Changes "Banana" to "Mango"

string found = fruits["apple"]; // "Apple" (case-insensitive)

Access Modifiers​

Access modifiers control the accessibility of classes and their members.

// Public class (accessible from any assembly)
public class PublicClass
{
// Public member (accessible from any code)
public void PublicMethod() { }

// Private member (accessible only within the containing class)
private void PrivateMethod() { }

// Protected member (accessible within the containing class and derived classes)
protected void ProtectedMethod() { }

// Internal member (accessible within the containing assembly)
internal void InternalMethod() { }

// Protected internal member (accessible within the containing assembly or derived classes)
protected internal void ProtectedInternalMethod() { }

// Private protected member (C# 7.2+, accessible within the containing class or derived classes in the same assembly)
private protected void PrivateProtectedMethod() { }
}

// Internal class (accessible only within the containing assembly)
internal class InternalClass
{
// Members here...
}

// Class with no access modifier is internal by default
class DefaultClass
{
// Members here...
}

Static Classes and Members​

Static classes and members belong to the class itself rather than to instances of the class.

// Static class (cannot be instantiated)
public static class MathUtility
{
// Static field
public static double Pi = 3.14159;

// Static property
public static double E { get; } = 2.71828;

// Static method
public static double Square(double number)
{
return number * number;
}

// Static constructor (called once before any static member is accessed)
static MathUtility()
{
Console.WriteLine("MathUtility initialized");
}
}

// Regular class with static members
public class Counter
{
// Instance field
private int _count;

// Static field (shared across all instances)
private static int _totalCount;

// Instance property
public int Count
{
get { return _count; }
set { _count = value; }
}

// Static property
public static int TotalCount
{
get { return _totalCount; }
}

// Instance method
public void Increment()
{
_count++;
_totalCount++;
}

// Static method
public static void Reset()
{
_totalCount = 0;
// _count = 0; // Error: Cannot access instance field from static method
}
}

// Usage
double area = MathUtility.Pi * MathUtility.Square(5); // Using static class

Counter c1 = new Counter();
Counter c2 = new Counter();

c1.Increment(); // c1.Count = 1, Counter.TotalCount = 1
c2.Increment(); // c2.Count = 1, Counter.TotalCount = 2
c1.Increment(); // c1.Count = 2, Counter.TotalCount = 3

Console.WriteLine(Counter.TotalCount); // 3
Counter.Reset(); // Resets TotalCount to 0

Inheritance​

Inheritance allows a class to inherit fields, properties, and methods from another class.

// Base class
public class Animal
{
// Protected field (accessible in derived classes)
protected string _name;

// Public property
public string Species { get; set; }

// Constructor
public Animal(string name, string species)
{
_name = name;
Species = species;
}

// Virtual method (can be overridden in derived classes)
public virtual void MakeSound()
{
Console.WriteLine("Animal makes a sound");
}

// Non-virtual method
public void Sleep()
{
Console.WriteLine($"{_name} is sleeping");
}
}

// Derived class
public class Dog : Animal
{
// Additional property
public string Breed { get; set; }

// Constructor that calls the base class constructor
public Dog(string name, string breed) : base(name, "Canine")
{
Breed = breed;
}

// Override the virtual method
public override void MakeSound()
{
Console.WriteLine($"{_name} barks");
}

// New method specific to Dog
public void Fetch()
{
Console.WriteLine($"{_name} is fetching");
}
}

// Another derived class
public class Cat : Animal
{
// Constructor
public Cat(string name) : base(name, "Feline")
{
}

// Override the virtual method
public override void MakeSound()
{
Console.WriteLine($"{_name} meows");
}

// Method that hides the base class method (not recommended, use override instead)
public new void Sleep()
{
Console.WriteLine($"{_name} is taking a cat nap");
}
}

// Usage
Animal animal = new Animal("Generic Animal", "Unknown");
animal.MakeSound(); // "Animal makes a sound"

Dog dog = new Dog("Buddy", "Golden Retriever");
dog.MakeSound(); // "Buddy barks"
dog.Sleep(); // "Buddy is sleeping" (inherited from Animal)
dog.Fetch(); // "Buddy is fetching"

Cat cat = new Cat("Whiskers");
cat.MakeSound(); // "Whiskers meows"
cat.Sleep(); // "Whiskers is taking a cat nap"

// Polymorphism
Animal dogAsAnimal = new Dog("Rex", "German Shepherd");
dogAsAnimal.MakeSound(); // "Rex barks" (calls the overridden method)
dogAsAnimal.Sleep(); // "Rex is sleeping" (calls the base class method)
// dogAsAnimal.Fetch(); // Error: Animal doesn't have a Fetch method

Animal catAsAnimal = new Cat("Felix");
catAsAnimal.Sleep(); // "Felix is sleeping" (calls the base class method, not the hiding method)

Abstract Classes and Methods​

Abstract classes cannot be instantiated and may contain abstract methods that derived classes must implement.

// Abstract base class
public abstract class Shape
{
// Regular property
public string Color { get; set; }

// Constructor
public Shape(string color)
{
Color = color;
}

// Abstract method (must be implemented by derived classes)
public abstract double CalculateArea();

// Virtual method (can be overridden, but has a default implementation)
public virtual void Display()
{
Console.WriteLine($"A {Color} shape");
}

// Regular method
public void SetColor(string color)
{
Color = color;
}
}

// Concrete derived class
public class Circle : Shape
{
public double Radius { get; set; }

public Circle(double radius, string color) : base(color)
{
Radius = radius;
}

// Implementation of abstract method
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}

// Override of virtual method
public override void Display()
{
Console.WriteLine($"A {Color} circle with radius {Radius}");
}
}

// Another concrete derived class
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }

public Rectangle(double width, double height, string color) : base(color)
{
Width = width;
Height = height;
}

// Implementation of abstract method
public override double CalculateArea()
{
return Width * Height;
}

// Override of virtual method
public override void Display()
{
Console.WriteLine($"A {Color} rectangle with dimensions {Width}x{Height}");
}
}

// Usage
// Shape shape = new Shape("Red"); // Error: Cannot create an instance of an abstract class

Circle circle = new Circle(5.0, "Blue");
Console.WriteLine($"Circle area: {circle.CalculateArea()}"); // ~78.54
circle.Display(); // "A Blue circle with radius 5"

Rectangle rectangle = new Rectangle(4.0, 6.0, "Green");
Console.WriteLine($"Rectangle area: {rectangle.CalculateArea()}"); // 24
rectangle.Display(); // "A Green rectangle with dimensions 4x6"

// Polymorphism with abstract base class
Shape shapeRef = circle;
Console.WriteLine($"Shape area: {shapeRef.CalculateArea()}"); // ~78.54 (calls Circle's implementation)

Sealed Classes and Methods​

Sealed classes cannot be inherited, and sealed methods cannot be overridden in derived classes.

// Base class
public class Vehicle
{
public virtual void Start()
{
Console.WriteLine("Vehicle starting");
}

public virtual void Stop()
{
Console.WriteLine("Vehicle stopping");
}
}

// Derived class with a sealed method
public class Car : Vehicle
{
// Sealed override method (cannot be overridden in derived classes)
public sealed override void Start()
{
Console.WriteLine("Car starting");
}

public override void Stop()
{
Console.WriteLine("Car stopping");
}
}

// Sealed class (cannot be inherited)
public sealed class ElectricCar : Car
{
// Can override non-sealed methods
public override void Stop()
{
Console.WriteLine("Electric car stopping");
}

// Cannot override sealed methods
// public override void Start() { } // Error: Cannot override sealed method
}

// Cannot derive from a sealed class
// public class LuxuryElectricCar : ElectricCar { } // Error: Cannot inherit from sealed class

Interfaces​

Interfaces define a contract that implementing classes must follow.

// Interface declaration
public interface IShape
{
// Property signature (no implementation)
double Area { get; }

// Method signature (no implementation)
void Draw();

// Default interface method (C# 8.0+)
void PrintInfo()
{
Console.WriteLine($"Shape with area: {Area}");
}
}

// Another interface
public interface IColorable
{
string Color { get; set; }
void SetColor(string color);
}

// Class implementing a single interface
public class Circle : IShape
{
public double Radius { get; set; }

public Circle(double radius)
{
Radius = radius;
}

// Implementing the Area property
public double Area => Math.PI * Radius * Radius;

// Implementing the Draw method
public void Draw()
{
Console.WriteLine($"Drawing a circle with radius {Radius}");
}
}

// Class implementing multiple interfaces
public class Rectangle : IShape, IColorable
{
public double Width { get; set; }
public double Height { get; set; }
public string Color { get; set; }

public Rectangle(double width, double height, string color)
{
Width = width;
Height = height;
Color = color;
}

// Implementing IShape.Area
public double Area => Width * Height;

// Implementing IShape.Draw
public void Draw()
{
Console.WriteLine($"Drawing a {Color} rectangle with dimensions {Width}x{Height}");
}

// Implementing IColorable.SetColor
public void SetColor(string color)
{
Color = color;
}
}

// Usage
Circle circle = new Circle(5.0);
circle.Draw(); // "Drawing a circle with radius 5"
Console.WriteLine($"Circle area: {circle.Area}"); // ~78.54
circle.PrintInfo(); // "Shape with area: 78.54" (default interface method)

Rectangle rectangle = new Rectangle(4.0, 6.0, "Blue");
rectangle.Draw(); // "Drawing a Blue rectangle with dimensions 4x6"
Console.WriteLine($"Rectangle area: {rectangle.Area}"); // 24
rectangle.SetColor("Red"); // Changes color to "Red"

// Using interfaces for polymorphism
IShape shape = circle;
shape.Draw(); // "Drawing a circle with radius 5"

IColorable colorable = rectangle;
colorable.SetColor("Green"); // Changes color to "Green"

Interface Inheritance​

Interfaces can inherit from other interfaces.

// Base interface
public interface IEntity
{
string Id { get; }
DateTime CreatedDate { get; }
}

// Interface inheriting from another interface
public interface IAuditable : IEntity
{
string CreatedBy { get; }
DateTime? LastModifiedDate { get; }
string LastModifiedBy { get; }
}

// Interface with multiple inheritance
public interface IFullAuditable : IAuditable, ISoftDelete
{
bool IsActive { get; set; }
}

// Another interface
public interface ISoftDelete
{
bool IsDeleted { get; set; }
DateTime? DeletedDate { get; set; }
string DeletedBy { get; set; }
}

// Class implementing an interface with inheritance
public class User : IAuditable
{
public string Id { get; set; } // From IEntity
public DateTime CreatedDate { get; set; } // From IEntity
public string CreatedBy { get; set; } // From IAuditable
public DateTime? LastModifiedDate { get; set; } // From IAuditable
public string LastModifiedBy { get; set; } // From IAuditable

// Additional properties
public string Username { get; set; }
public string Email { get; set; }
}

Explicit Interface Implementation​

Explicit interface implementation allows a class to implement multiple interfaces that have members with the same name.

// Two interfaces with a method of the same name
public interface IDrawable
{
void Draw();
}

public interface IPrintable
{
void Print();
void Draw(); // Same name as IDrawable.Draw
}

// Class implementing both interfaces explicitly
public class Document : IDrawable, IPrintable
{
// Explicit implementation of IDrawable.Draw
void IDrawable.Draw()
{
Console.WriteLine("Drawing for display");
}

// Explicit implementation of IPrintable.Draw
void IPrintable.Draw()
{
Console.WriteLine("Drawing for printing");
}

// Regular implementation of IPrintable.Print
public void Print()
{
Console.WriteLine("Printing document");
}

// Class's own Draw method
public void Draw()
{
Console.WriteLine("Drawing document (class method)");
}
}

// Usage
Document doc = new Document();
doc.Draw(); // "Drawing document (class method)"
doc.Print(); // "Printing document"

// Accessing explicit interface implementations
((IDrawable)doc).Draw(); // "Drawing for display"
((IPrintable)doc).Draw(); // "Drawing for printing"

IDrawable drawable = doc;
drawable.Draw(); // "Drawing for display"

IPrintable printable = doc;
printable.Draw(); // "Drawing for printing"
printable.Print(); // "Printing document"

Polymorphism​

Polymorphism allows objects of different types to be treated as objects of a common base type.

// Base class
public class Shape
{
public virtual double CalculateArea()
{
return 0;
}

public virtual void Draw()
{
Console.WriteLine("Drawing a shape");
}
}

// Derived classes
public class Circle : Shape
{
public double Radius { get; set; }

public Circle(double radius)
{
Radius = radius;
}

public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}

public override void Draw()
{
Console.WriteLine($"Drawing a circle with radius {Radius}");
}
}

public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }

public Rectangle(double width, double height)
{
Width = width;
Height = height;
}

public override double CalculateArea()
{
return Width * Height;
}

public override void Draw()
{
Console.WriteLine($"Drawing a rectangle with dimensions {Width}x{Height}");
}
}

// Usage
Shape shape1 = new Circle(5.0);
Shape shape2 = new Rectangle(4.0, 6.0);

// Polymorphic behavior
shape1.Draw(); // "Drawing a circle with radius 5"
shape2.Draw(); // "Drawing a rectangle with dimensions 4x6"

Console.WriteLine($"Area of shape1: {shape1.CalculateArea()}"); // ~78.54
Console.WriteLine($"Area of shape2: {shape2.CalculateArea()}"); // 24

// List of different shapes treated as a common type
List<Shape> shapes = new List<Shape>
{
new Circle(3.0),
new Rectangle(2.0, 5.0),
new Circle(4.0)
};

// Process all shapes polymorphically
foreach (Shape shape in shapes)
{
shape.Draw();
Console.WriteLine($"Area: {shape.CalculateArea()}");
}

Encapsulation​

Encapsulation is the bundling of data and methods that operate on that data within a single unit (class) and restricting access to some of the object's components.

public class BankAccount
{
// Private fields (encapsulated data)
private string _accountNumber;
private decimal _balance;
private readonly List<string> _transactionHistory = new List<string>();

// Constructor
public BankAccount(string accountNumber, decimal initialDeposit)
{
if (string.IsNullOrWhiteSpace(accountNumber))
throw new ArgumentException("Account number cannot be empty", nameof(accountNumber));

if (initialDeposit < 0)
throw new ArgumentException("Initial deposit cannot be negative", nameof(initialDeposit));

_accountNumber = accountNumber;
_balance = initialDeposit;
RecordTransaction($"Initial deposit: {initialDeposit:C}");
}

// Public properties (controlled access to private fields)
public string AccountNumber => _accountNumber; // Read-only

public decimal Balance => _balance; // Read-only

public IReadOnlyList<string> TransactionHistory => _transactionHistory.AsReadOnly(); // Read-only view

// Public methods (controlled operations on private data)
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Deposit amount must be positive", nameof(amount));

_balance += amount;
RecordTransaction($"Deposit: {amount:C}");
}

public bool Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Withdrawal amount must be positive", nameof(amount));

if (amount > _balance)
{
RecordTransaction($"Withdrawal failed: Insufficient funds for {amount:C}");
return false;
}

_balance -= amount;
RecordTransaction($"Withdrawal: {amount:C}");
return true;
}

// Private helper method
private void RecordTransaction(string transaction)
{
_transactionHistory.Add($"{DateTime.Now}: {transaction}");
}
}

// Usage
BankAccount account = new BankAccount("123456789", 1000);
Console.WriteLine($"Account: {account.AccountNumber}, Balance: {account.Balance:C}");

account.Deposit(500);
Console.WriteLine($"New balance: {account.Balance:C}"); // $1,500.00

bool withdrawalSuccess = account.Withdraw(2000);
Console.WriteLine($"Withdrawal successful: {withdrawalSuccess}"); // false
Console.WriteLine($"Balance after withdrawal attempt: {account.Balance:C}"); // Still $1,500.00

withdrawalSuccess = account.Withdraw(700);
Console.WriteLine($"Withdrawal successful: {withdrawalSuccess}"); // true
Console.WriteLine($"Balance after withdrawal: {account.Balance:C}"); // $800.00

// View transaction history
Console.WriteLine("Transaction History:");
foreach (string transaction in account.TransactionHistory)
{
Console.WriteLine(transaction);
}

// Cannot modify private fields directly
// account._balance = 1000000; // Error: _balance is inaccessible
// account.TransactionHistory.Add("Fraudulent transaction"); // Error: Cannot modify a read-only collection

Object Initialization and Object Initializers​

Object initializers provide a concise syntax to set properties during object creation.

// Class with properties
public class Person
{
// Auto-implemented properties
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Email { get; set; }

// Default constructor
public Person()
{
}

// Parameterized constructor
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}

// Method
public string GetFullName() => $"{FirstName} {LastName}";
}

// Usage
// Traditional way (without object initializer)
Person person1 = new Person();
person1.FirstName = "John";
person1.LastName = "Doe";
person1.Age = 30;
person1.Email = "john.doe@example.com";

// Using object initializer syntax
Person person2 = new Person
{
FirstName = "Jane",
LastName = "Smith",
Age = 25,
Email = "jane.smith@example.com"
};

// Using object initializer with constructor
Person person3 = new Person("Bob", "Johnson")
{
Age = 40,
Email = "bob.johnson@example.com"
};

// Collection initializers
List<Person> people = new List<Person>
{
new Person { FirstName = "Alice", LastName = "Brown", Age = 35 },
new Person { FirstName = "Charlie", LastName = "Davis", Age = 28 },
new Person("David", "Wilson") { Age = 45 }
};

// Dictionary initializers
Dictionary<string, Person> personDict = new Dictionary<string, Person>
{
["employee1"] = new Person { FirstName = "Eve", LastName = "Taylor" },
["employee2"] = new Person { FirstName = "Frank", LastName = "Miller" }
};

// Anonymous types (C# 3.0+)
var anonymousPerson = new
{
FirstName = "Grace",
LastName = "Lee",
Age = 32,
Email = "grace.lee@example.com"
};

Console.WriteLine($"{anonymousPerson.FirstName} {anonymousPerson.LastName}, {anonymousPerson.Age}");

Partial Classes and Methods​

Partial classes allow splitting a class definition across multiple files.

// File1.cs
public partial class Customer
{
// Fields
private string _name;
private string _email;

// Constructor
public Customer(string name, string email)
{
_name = name;
_email = email;
}

// Properties
public string Name
{
get { return _name; }
set { _name = value; }
}

public string Email
{
get { return _email; }
set { _email = value; }
}

// Partial method declaration (must be implemented in another partial class)
partial void OnNameChanged(string oldName, string newName);

// Method that calls the partial method
public void ChangeName(string newName)
{
string oldName = _name;
_name = newName;
OnNameChanged(oldName, newName); // Will only execute if implemented
}
}

// File2.cs
public partial class Customer
{
// Additional fields
private List<string> _orders = new List<string>();

// Additional properties
public IReadOnlyList<string> Orders => _orders.AsReadOnly();

// Additional methods
public void AddOrder(string orderNumber)
{
_orders.Add(orderNumber);
Console.WriteLine($"Order {orderNumber} added for {_name}");
}

// Implementation of the partial method
partial void OnNameChanged(string oldName, string newName)
{
Console.WriteLine($"Name changed from {oldName} to {newName}");
}
}

// Usage
Customer customer = new Customer("John Doe", "john.doe@example.com");
customer.AddOrder("ORD-12345");
customer.ChangeName("John Smith"); // Triggers the OnNameChanged partial method

Resources​