Classes
Intro to Classes
What is a class?
A class is a building block of an application. All these classes together provide the behaviour of an application.
A class has 2 parts:
Data represented by fields
Behaviour represented by methods
To declare a class:
Note: public
is an access modifier that you'll learn about later.
Examples of classes
In a real-world application, there are usually 3 layers to any application:
Presentation layer
How the data looks
Business layer
The business logic
Persistence layer
Data access
Example:
PostView
displays post information to a userPost
handles the logic of postsPostRepository
knows how to save to or read from a database
What is an object?
Objects are instances of classes. If Person
is a class, then John
, Mary
, and Scott
are objects of that class.
To instantiate an object:
Static members
Some fields and methods are accessible from class instances. These are called Instance members.
On the other hand, some fields and methods are accessible from the class itself. These are called static members.
Notice how we use the static
access modifier to set a static member!
Pro tip: We use static members when the concept we are representing is a singleton, i.e., it only needs one place in memory and not duplicates. For example, DateTime.Now
only needs one instance. It's redundant to duplicate the current time in different objects.
Constructors
A constructor is simply a method that is called when an instance of a class is created. This is useful for initializing some fields in the class.
Important details:
A constructor must have the same name as the class.
The above constructor is known as a parameter-less or default constructor.
If you don't provide a constructor yourself, the compiler will automatically add a default constructor in for you.
If any fields don't get initialized, they fallback to their default values:
int
becomes0
bool
becomesfalse
char
becomes''
reference types like
string
and objects becomenull
Here's a constructor with actual parameters:
Note: The this
keyword references the current object being initialized.
Constructor overloading
In classes, you can utilize constructor overloading where you define more than one constructor to account for different parameters.
Constructor overloading allows your class to be used more flexibly. For example, sometimes you don't know the id
of a customer, their name
, or both. You want to be able to create a customer in any of these cases.
Note: When you constructor overload, your class won't automatically create the default parameter-less constructor for you, so you need to explicitly define it like above.
Important: A constructor is considered unique based on the order of the parameter data types.
Calling constructors inside other constructors
Sometimes one of your class' constructors has all of the initialization you want in another constructor. In this case, it would be great to be able to re-use a constructor inside another constructor.
For example, whenever you create a field containing a data type like a List
, you need to initialize it before it can be used (otherwise it will default to null
).
In cases like the above (and more), you don't want to have to explicitly initialize the List
in every other constructor. Instead, you can re-use your constructors inside other constructors:
this()
and this(id)
always get called before the constructor runs. So in effect, you are stacking constructor initialization.
Pro tip: Stacking constructor initialization, although possible, is not recommended due to the way it requires you to trace what's going on. It's a better idea to limit this stacking to initialization that you have to perform—like initializing a List
.
Object Initializers
Object initializers allow you to initialize an object without using a class constructor.
The reason object initializers were added to C# was they prevented the need to constructor overload to account for every possible combination of initialized values.
Instead, the idea is to restrict constructors to initialization that must happen in order for the object to work.
Note: When you initialize an object using an object initializer, the default constructor still runs first.
Methods
Signature of methods
The signature of a method is basically its
Name, plus
The number and type of parameters
Method overloading
Just like how you can overload constructors, you can overload methods.
This is especially useful when you want to give the consumer of your class more options around that method.
Params modifier
Suppose you have a method that could take a varying number of parameters. For example, the Add
method might need that.
Overloading, like in the case above, wouldn't work because there's an infinite number of cases to consider.
Well, what if you passed an array instead?
This works! But there's only one downside: every single time you want to pass arguments to Add
, you have to place the data in an array. That's a bit awkward.
To solve this, C# gives us the params modifier. This modifier collects all of your method's parameters into an array for you.
Ref modifier
The ref modifier allows you to pass the reference for a variable, so you can update the value of that variable inside your method.
Notice how ref
is used for the argument and the parameter. This is required.
Note: The ref modifier is not best practice, so please try to avoid it.
Out modifier
The out modifier is pretty much identical to the ref modifier with the exception that you don't have to initialize the variable before passing it into the method. However, you do have to initialize the variable inside the method.
You may use the out modifier at times though, so it's good to know. One common use case is with int.TryParse
. This method allows you to convert a string into an int without throwing an exception if it fails.
Fields
If you recall, fields are like variables inside of a class.
Initialization
You've seen that initialization of a field can happen inside a constructor:
However, some developers believe constructors should be reserved for only when you want to initialize fields based on values passed from the outside.
To preserve this approach to constructors, we can initialize inline:
Pro tip: Both approaches are reasonable. Just make sure you're consistent.
Read-only fields
The readonly
modifier sets it so that a field can only be initialized once (either inline or in the constructor).
This is useful when you want to make sure some data doesn't get overwritten by accident.
Access Modifiers
An access modifier controls access to a class and its members. You've seen some like public
before. Here is a list of all of them:
public
private
protected
internal
protected internal
We only care about public
and private
here. The others have more to do with inheritance.
Why access matters: Just like the readonly
modifier, it improves safety of our code, preventing bugs.
Encapsulation
In the modern factory approach to work, we separate out responsibilities and encapsulate it to roles. A chef doesn't need to know what a waitress is responsible for and vice versa, but they help each other.
In software, especially object-oriented programming, each class is similarly encapsulated. One class shouldn't need to know how another class does what it's responsible for. It should just be able to use that class like a black box.
Also known as information hiding, encapsulation is the hiding of data (fields) or behaviour/functionality (methods) in classes.
Encapsulation in practice
In practice, encapsulation in C# means 2 things:
Hide fields using
private
, andAccess them through getter and setter methods that are
public
.
We want to hide fields because they are considered an implementation detail. We don't want to give access to how the class works, only what will make it work.
Note: One of the benefits of the setter/getter pattern is you can perform logic checks before initializing or retrieving fields. In the example above, we don't initialize name unless it has a real value.
Note on convention:
Notice how we name the private field
_name
. By convention, private fields are always formatted as camel case prepended with a_
.Whereas the class itself and its methods are formatted as pascal case.
Properties
Properties are class members that encapsulate a setter and getter for accessing a field. It basically allows you to create a setter and getter with less code.
Note on convention: The property is pascal case as well!
To shorten this code even more, C# can automatically set the private field for us and implement the property's setter and getter. This is known as an auto-implemented property. Just follow this syntax:
Note: Notice how it looks like we're directly updating a field. This isn't true. Behind the scenes, C# is using the getter and setter to update the field internally.
Customizing the setter in a Property
If you want to only be able to set a property once and never again, you can (1) initialize the property in a constructor plus (2) set the setter to private
.
On the other hand, if you want a property to be dynamically determined without the ability to manually set it, you can (1) customize how the getter works plus (2) not create a setter.
Indexers
An indexer is a way to access elements in a class that represents a list of values.
You've seen indexers before in arrays and lists:
To build your own indexer in a class, it's literally just like building a property:
Things to note:
Notice how we use
this
because the indexer doesn't have a name.string key
is the indexer.
Last updated