Classes
Overview
Classes are fundamental building blocks in object-oriented programming that define the structure and behavior of objects. They encapsulate data for the object and methods to manipulate and use that data.
Syntax
A class is defined using the class
keyword, followed by the class name and its body enclosed in curly braces {}
.
Example
public class Person
{
// Fields, properties, and methods go here
}
Fields
Fields are variables that are declared directly in a class. They store the data for the object.
Example
public class Person
{
public string Name;
}
You can interact with fields using dot notation:
var person = new Person();
person.Name = "John Doe";
Properties
Properties mainly provide controlled access to fields.
Example
public class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
In this example, value
is a special keyword that represents the value being assigned to the property:
public class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Person person = new Person();
person.Name = "John Doe";
This is consider the "legacy" way of defining properties. Let's look at more consise ways of declaring properties.
public class Person
{
public string Name { get; set; } //creates a private backing field under the hood
}
Mutability of properties/fields
- Mutable: Properties/fields that can be modified.
- Immutable: Properties/fields that cannot be modified after initialization.
public class Person
{
public string Name { get; } // Immutable property, typically set in constructor
public int Age { get; set; } // Mutable property
}
var person = new Person { Name = "John Doe", Age = 30 };
person.Age = 31; // This is fine
person.Name = "Jane Doe"; // This will cause a compile-time error
Read-only properties
Read-only properties can only be set in the constructor as we mentioned:
public class Person
{
public string Name { get; }
}
However, you can also have properties that return a computed value:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
}
The =>
syntax denotes that this property is a read-only computed property. It wouldn't make sense to set full name and then have it set first and last name properties.
Constructors
Special methods called when an object is instantiated. They initialize the object.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Person person = new Person("John Doe", 30);
Console.WriteLine(person.Name); // John Doe
Classes can contain multiple constructors:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public Person(string name)
{
Name = name;
Age = 0;
}
}
Person person = new Person("John Doe", 30);
Person person2 = new Person("John Doe");
Console.WriteLine(person.Name); // John Doe
Console.WriteLine(person2.Name); // John Doe
You can use the this
keyword to call the current class's constructor from another constructor:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public Person(string name) : this(name, 0)
{
}
}
Primary Constructor
Simplified syntax for constructors that allows initializing properties directly in the class definition.
public class Person(string Name, int Age)
{
// Properties automatically created
}
🌶️🌶️🌶️ Spencer rarely uses primary constructors because he doesn't like mixing multiple ways of declaring constructors, and Spencer does use multiple constructors fairly often.
Using Constructors to Create Objects
The new
keyword is used to create an instance of a class. Method syntax is used to pass parameters to the constructor.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Person person = new Person("John Doe", 30);
Thrown exceptions in constructors
If you throw an exception in a constructor, the object is not instantiated.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Name cannot be null or empty", nameof(name));
}
Name = name;
Age = age;
}
}
Person person = new Person("John Doe", 30);
Person person2 = new Person(string.Empty, 30); // This will throw an exception
Using object initializers
Object initializers provide a way to initialize objects with some default properties. The "really really old way" of adding values to properties after construction looked like this:
Person person = new Person();
person.Name = "John Doe";
person.Age = 30; //😕
The more common and modern syntax looks like this:
Person person = new Person {
Name = "John Doe",
Age = 30 //❤️
};
Properties can be made immutable but settable using the object initializer syntax by using the init
keyword:
public class Person
{
public string Name { get; init; }
public int Age { get; init; }
}
Person person = new Person {
Name = "John Doe",
Age = 30
};
You can also force the developer to set the property using object initializer syntax by using the required
keyword:
public class Person
{
public required string Name { get; init; }
public required int Age { get; init; }
}
🌶️🌶️🌶️ I love object initializers, full stop. That said, I don't use init
and required
super often, except in newer codebases.
Equality of classes
Classes are reference types, which means that they are compared by reference, not value. This means that two classes are considered equal if they reference the same object in memory, but not if they have the same values.
Person person1 = new Person { Name = "John Doe", Age = 30 };
Person person2 = new Person { Name = "John Doe", Age = 30 };
Console.WriteLine(person1 == person2); // False
person1.Equals(person2); // False
Overriding Equals
and GetHashCode
is a good way to customize how classes are compared:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType()) return false;
Person other = (Person)obj;
return Name == other.Name && Age == other.Age;
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Age);
}
}
BTW, GetHashCode
is mainly used in hash tables, like the Dictionary
type.
IEquatable<T>
Definition:
Interface for providing a strongly-typed method to check equality.
Examples:
public class Person : IEquatable<Person>
{
public string Name { get; set; }
public int Age { get; set; }
public bool Equals(Person? other)
{
if (other == null) return false;
return Name == other.Name && Age == other.Age;
}
}
What the heck is this angle bracket IEquatable<Person>
syntax? Is this HTML or what?? Patience, grasshopper - more on this later.