Classes and Objects in C#
Introduction to Classes and Objects
Classes and objects are the foundational building blocks of Object-Oriented Programming in C#. A class is a blueprint or template that defines the structure and behavior of objects, while an object is an instance of a class.
Class vs Object
Class | Object |
---|---|
A template or blueprint | An instance of a class |
Defines properties and behaviors | Contains actual data and can perform actions |
Created once | Can create multiple instances |
Exists at compile time | Exists at runtime |
Defining a Class in C#
A class in C# is defined using the class
keyword. Classes can contain:
- Fields (variables)
- Properties
- Methods (functions)
- Constructors
- Events
- Nested classes
Basic Class Structure
// Basic class definition
public class Person
{
// Fields - variables that store data
private string firstName;
private string lastName;
private int age;
// Properties - provide access to fields with additional logic
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
// Auto-implemented property - compiler creates backing field
public string FullName => $"{FirstName} {LastName}";
// Method - provides behavior
public void Introduce()
{
Console.WriteLine($"Hello, my name is {FullName} and I am {age} years old.");
}
// Method with parameters and return value
public bool IsAdult()
{
return age >= 18;
}
}
Key components of a class:
- Access modifiers (public, private, protected, internal) control visibility
- Fields store the data (typically private)
- Properties provide controlled access to fields
- Methods define the behavior of the class
Creating and Using Objects
Objects are instances of classes created at runtime. To create an object in C#, you use the new
keyword.
Object Instantiation and Usage
// Creating an object from the Person class
Person person1 = new Person();
// Setting property values
person1.FirstName = "John";
person1.LastName = "Doe";
// Using methods
person1.Introduce(); // Output: Hello, my name is John Doe and I am 0 years old.
// Another way to create and initialize an object
Person person2 = new Person
{
FirstName = "Jane",
LastName = "Smith"
};
// Access a property
Console.WriteLine(person2.FullName); // Output: Jane Smith
// Create multiple objects from the same class
Person employee = new Person();
Person customer = new Person();
Person manager = new Person();
Working with objects:
- Instantiation - Creating an instance of a class using the
new
keyword - Object initializers - Setting property values while creating the object
- Accessing members - Using dot notation to access properties and methods
- Multiple instances - Creating many objects from the same class template
Fields vs Properties
Fields and properties both store data in a class, but properties provide additional control over access and manipulation of that data.
Fields
public class Customer
{
// Fields
private string name; // Private field - not accessible outside the class
public int customerID; // Public field - accessible anywhere
internal DateTime registrationDate; // Internal field - accessible within the same assembly
protected bool isActive; // Protected field - accessible in this class and derived classes
}
Properties
public class Customer
{
// Private backing field
private string _name;
// Full property with get and set accessors
public string Name
{
get
{
return _name ?? "No Name"; // Null coalescing - return "No Name" if _name is null
}
set
{
if (!string.IsNullOrWhiteSpace(value))
{
_name = value;
}
}
}
// Auto-implemented property (compiler creates backing field)
public int CustomerID { get; set; }
// Read-only property (can only be set in constructor or initializer)
public DateTime RegistrationDate { get; }
// Read-only calculated property
public bool IsLongTermCustomer => (DateTime.Now - RegistrationDate).TotalDays > 365;
// Property with different access levels for get and set
public bool IsActive { get; private set; }
}
Property types:
- Full property - Custom get and set accessors with a backing field
- Auto-implemented property - Simplified syntax where compiler creates the backing field
- Read-only property - Has only a get accessor
- Calculated property - Computes its value from other data
- Mixed access levels - Different visibility for get and set accessors
Class Members and Access Modifiers
Access modifiers control the visibility and accessibility of class members.
Access Modifiers in C#
Modifier | Description |
---|---|
public |
Accessible from anywhere |
private |
Accessible only within the same class |
protected |
Accessible within the same class and derived classes |
internal |
Accessible within the same assembly (project) |
protected internal |
Accessible within the same assembly or derived classes |
private protected |
Accessible within the same class or derived classes in the same assembly |
Example of Access Modifiers
public class BankAccount
{
// Private field - only accessible in this class
private decimal balance;
// Public property - accessible from anywhere
public string AccountNumber { get; set; }
// Protected method - accessible in this class and derived classes
protected void UpdateLastAccessed()
{
LastAccessed = DateTime.Now;
}
// Internal property - accessible only in the same assembly
internal DateTime LastAccessed { get; private set; }
// Public method - accessible from anywhere
public decimal GetBalance()
{
UpdateLastAccessed();
return balance;
}
}
// Derived class
public class SavingsAccount : BankAccount
{
public void ApplyInterest()
{
// Can access protected members from the base class
UpdateLastAccessed();
// Can also access internal members since it's in the same assembly
DateTime lastAccess = LastAccessed;
// Cannot access private members of the base class
// balance = 100; // This would cause a compilation error
}
}
Static and Instance Members
C# classes can have both static members (shared across all instances) and instance members (unique to each object).
Static vs Instance Members
public class Calculator
{
// Static field - shared across all Calculator objects
public static double Pi = 3.14159;
// Static property - shared across all Calculator objects
public static int CalculationsPerformed { get; private set; }
// Instance field - unique to each Calculator object
public string Model;
// Instance property - unique to each Calculator object
public bool IsScientific { get; set; }
// Static method - accessed through the class, not an instance
public static double CalculateCircleArea(double radius)
{
CalculationsPerformed++;
return Pi * radius * radius;
}
// Instance method - requires an instance to be called
public double Add(double a, double b)
{
CalculationsPerformed++;
return a + b;
}
}
// Usage
class Program
{
static void Main()
{
// Using static members (through the class)
double area = Calculator.CalculateCircleArea(5);
Console.WriteLine($"Circle area: {area}");
Console.WriteLine($"Pi value: {Calculator.Pi}");
// Using instance members (through objects)
Calculator calc1 = new Calculator { Model = "Basic", IsScientific = false };
Calculator calc2 = new Calculator { Model = "Advanced", IsScientific = true };
double sum1 = calc1.Add(5, 10);
double sum2 = calc2.Add(20, 30);
Console.WriteLine($"Total calculations: {Calculator.CalculationsPerformed}");
}
}
Key differences:
- Static members:
- Belong to the class itself, not to objects
- Accessed using the class name (e.g.,
Calculator.Pi
) - Shared among all instances of the class
- Exist even if no objects are created
- Instance members:
- Belong to specific objects
- Accessed using an object reference (e.g.,
calc1.Add()
) - Unique to each instance of the class
- Require an object instance to be created
Best Practices for Classes and Objects
Guidelines for Effective Class Design
- Encapsulation - Make fields private and provide access through properties
- Single Responsibility - A class should have only one reason to change
- Meaningful Names - Use clear, descriptive names for classes and members
- Keep Classes Focused - Avoid creating "god classes" that do too much
- Limit Class Size - If a class is too large, it might be better to split it
- Use Properties - Prefer properties over public fields
- Validate Input - Check input values in property setters and methods
- Immutability - Consider making classes immutable when appropriate
- Method Size - Keep methods short and focused on a single task
- Consistent Abstraction - Keep a consistent level of abstraction within a class
Common Mistakes to Avoid
- Public Fields - Avoid public fields; use properties instead
- Too Many Dependencies - A class that depends on too many other classes is hard to maintain
- Tight Coupling - Avoid tight coupling between classes
- Excessive Comments - If you need excessive comments, the code might be too complex
- Breaking Encapsulation - Avoid exposing internal implementation details
- Unused Members - Remove unused fields, properties, and methods
- Too Many Static Members - Overuse of static members can lead to global state problems